2021-12-29 18:08:41 -05:00
import * as fs from 'fs' ;
import { join , dirname , isAbsolute , resolve , sep } from 'path' ;
import { fileURLToPath } from 'url' ;
import * as crypto from 'crypto' ;
Replace deprecated request/request-promise with axios
request has been deprecated since 2020 with an unfixed SSRF advisory and
pins vulnerable copies of form-data (critical), qs, tough-cookie and
uuid - 8 of the 13 remaining production audit findings, none fixable by
version bumps (issue #1634, item 1).
All 36 backend files that imported request-promise now use a small
compatibility wrapper (server/utils/request.ts) backed by axios, which
is already a production dependency. The wrapper accepts the existing
options shape (qs, form - object or pre-encoded string, body,
baseUrl/uri, rejectUnauthorized, json), resolves with the response body
directly, and rejects with a plain object mirroring request-promise's
StatusCodeError/RequestError shape, so CommonService.handleError works
unchanged (ECONNREFUSED -> 503, Eclair StatusCodeError -> 500, nested
error body extraction). Auth headers are excluded from rejected errors
so they cannot leak into logs. Callers without json: true (block
explorer, currency rates) still get raw text bodies, and LND's
line-delimited /v2/router/send stream still surfaces as a string for
the existing parser.
Only behavioral code change: CLN verifyMessage used request-promise's
callback style and was ported to the same promise style as signMessage;
four Eclair handlers gained explicit returns to satisfy
noImplicitReturns once the import became typed.
Production npm audit drops from 13 findings (2 critical) to 6 low, all
in the crypto-browserify/elliptic chain tracked in #1634.
Verified against the docker regtest fixture with 43 API checks across
LND, Core Lightning and Eclair: reads, invoice creation, a routed LND
payment over the streaming endpoint, cross-implementation payments from
CLN and Eclair, message sign/verify, channel backup to disk, and
bad-invoice/node-unreachable error mapping. Lint and both production
builds are clean.
2026-07-19 12:08:05 -07:00
import request from './request.js' ;
2021-12-29 18:08:41 -05:00
import { Logger } from './logger.js' ;
export class CommonService {
constructor ( ) {
this . logger = Logger ;
this . nodes = [ ] ;
2024-06-10 12:40:37 -07:00
this . selectedNode = null ;
2024-11-18 20:27:51 -08:00
this . ssoInit = { rtlSSO : 0 , rtlCookiePath : '' , logoutRedirectLink : '' , cookieValue : '' } ;
2024-06-10 12:40:37 -07:00
this . appConfig = { defaultNodeIndex : 0 , selectedNodeIndex : 0 , rtlConfFilePath : '' , dbDirectoryPath : join ( dirname ( fileURLToPath ( import . meta . url ) ) , '..' , '..' ) , rtlPass : '' , allowPasswordUpdate : true , enable2FA : false , secret2FA : '' , SSO : this . ssoInit , nodes : [ ] } ;
2021-12-29 18:08:41 -05:00
this . port = 3000 ;
2023-02-17 17:33:33 -08:00
this . host = '' ;
2021-12-29 18:08:41 -05:00
this . secret _key = crypto . randomBytes ( 64 ) . toString ( 'hex' ) ;
this . read _dummy _data = false ;
this . baseHref = '/rtl' ;
this . dummy _data _array _from _file = [ ] ;
2022-11-02 15:59:37 -07:00
this . MONTHS = [
{ name : 'JAN' , days : 31 } , { name : 'FEB' , days : 28 } , { name : 'MAR' , days : 31 } , { name : 'APR' , days : 30 } , { name : 'MAY' , days : 31 } , { name : 'JUN' , days : 30 } ,
{ name : 'JUL' , days : 31 } , { name : 'AUG' , days : 31 } , { name : 'SEP' , days : 30 } , { name : 'OCT' , days : 31 } , { name : 'NOV' , days : 30 } , { name : 'DEC' , days : 31 }
] ;
2024-06-10 12:40:37 -07:00
this . maskPasswords = ( obj ) => {
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// Clone up front: masking a live config object must not blank the credentials LN
// requests authenticate with (mirrors removeSecureData).
const masked = JSON . parse ( JSON . stringify ( obj ) ) ;
const maskRecursive = ( current ) => {
const keys = Object . keys ( current ) ;
const length = keys . length ;
if ( length !== 0 ) {
for ( let i = 0 ; i < length ; i ++ ) {
// Header maps always carry credentials in this codebase (macaroon, rune, basic
// auth). Key-substring matching cannot catch them without also hiding the *Path
// fields the settings UI legitimately shows, so mask the whole map.
if ( keys [ i ] === 'headers' && current [ keys [ i ] ] && typeof current [ keys [ i ] ] === 'object' ) {
Object . keys ( current [ keys [ i ] ] ) . forEach ( ( headerKey ) => { current [ keys [ i ] ] [ headerKey ] = '*' . repeat ( 20 ) ; } ) ;
}
else if ( current [ keys [ i ] ] && typeof current [ keys [ i ] ] === 'object' ) {
// Truthiness guard: null is 'object' too and must not reach Object.keys.
maskRecursive ( current [ keys [ i ] ] ) ;
}
if ( typeof keys [ i ] === 'string' &&
( ( keys [ i ] . toLowerCase ( ) . includes ( 'password' ) && keys [ i ] !== 'allowPasswordUpdate' ) || keys [ i ] . toLowerCase ( ) . includes ( 'multipass' ) ||
keys [ i ] . toLowerCase ( ) . includes ( 'rpcpass' ) || keys [ i ] . toLowerCase ( ) . includes ( 'rpcpassword' ) ||
keys [ i ] . toLowerCase ( ) . includes ( 'rpcuser' ) || keys [ i ] . toLowerCase ( ) . includes ( 'rpcauth' ) ||
keys [ i ] . toLowerCase ( ) . includes ( 'secret2fa' ) || keys [ i ] . toLowerCase ( ) . includes ( 'cookievalue' ) ||
keys [ i ] . toLowerCase ( ) . includes ( 'rtlpass' ) || keys [ i ] . toLowerCase ( ) . includes ( 'runevalue' ) ) ) {
current [ keys [ i ] ] = '*' . repeat ( 20 ) ;
}
2024-06-10 12:40:37 -07:00
}
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
return current ;
} ;
return maskRecursive ( masked ) ;
2024-06-10 12:40:37 -07:00
} ;
this . removeAuthSecureData = ( node ) => {
2024-06-20 13:26:21 -07:00
if ( node . authentication ) {
delete node . authentication . macaroonPath ;
delete node . authentication . runePath ;
delete node . authentication . runeValue ;
delete node . authentication . lnApiPassword ;
delete node . authentication . options ;
}
2024-06-10 12:40:37 -07:00
return node ;
} ;
this . removeSecureData = ( config ) => {
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// Clone before deleting: cookieValue is runtime-only, so mutating a caller's live
// appConfig would destroy SSO state with no way to restore it.
const sanitized = JSON . parse ( JSON . stringify ( config ) ) ;
delete sanitized . rtlConfFilePath ;
delete sanitized . rtlPass ;
delete sanitized . multiPass ;
delete sanitized . multiPassHashed ;
delete sanitized . secret2FA ;
// The SSO cookie is a live bearer credential; it must never leave the server.
if ( sanitized . SSO ) {
delete sanitized . SSO . cookieValue ;
}
sanitized . nodes ? . forEach ( ( node ) => this . removeAuthSecureData ( node ) ) ;
return sanitized ;
2024-06-10 12:40:37 -07:00
} ;
this . addSecureData = ( config ) => {
config . rtlConfFilePath = this . appConfig . rtlConfFilePath ;
config . rtlPass = this . appConfig . rtlPass ;
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// Pin the hash only when the server holds one: on a default install's first boot the
// file already has multiPassHashed but the in-memory config does not, and pinning
// undefined would erase the only password from the file on save, bricking the boot.
if ( this . appConfig . multiPassHashed ) {
config . multiPassHashed = this . appConfig . multiPassHashed ;
}
else {
delete config . multiPassHashed ;
}
// Deployment-level switches are pinned to server-held values: the settings API must
// not flip the authentication mode (disableAuth, SSO) or move SSO fields, the
// password policy, or the database location; no UI flow writes them. Pinning the
// whole SSO object also means a trimmed or missing SSO object can never wipe server
// state.
config . disableAuth = this . appConfig . disableAuth ;
config . allowPasswordUpdate = this . appConfig . allowPasswordUpdate ;
config . dbDirectoryPath = this . appConfig . dbDirectoryPath ;
config . SSO = JSON . parse ( JSON . stringify ( this . appConfig . SSO || { } ) ) ;
2024-06-10 12:40:37 -07:00
if ( this . appConfig . multiPass ) {
config . multiPass = this . appConfig . multiPass ;
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// Restore the TOTP seed when the client omits it — and when it sends an empty seed
// while still claiming 2FA is on (an inconsistent pair no honest flow produces).
// The settings UI's enable flow sends a non-empty seed; its disable flow sends an
// empty seed with enable2FA false. Both are honored.
if ( config . secret2FA === undefined || ( config . secret2FA === '' && config . enable2FA ) ) {
2024-06-10 12:40:37 -07:00
config . secret2FA = this . appConfig . secret2FA ;
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// enable2FA derives from the seed, matching the boot-time derivation in config.ts,
// so the two fields can never diverge after a save.
config . enable2FA = ! ! config . secret2FA ;
2026-05-14 11:59:49 +02:00
const appConfigNodes = new Map ( this . appConfig . nodes ? . map ( ( node ) => [ node . index , node ] ) || [ ] ) ;
2026-05-14 11:51:10 +02:00
config . nodes ? . forEach ( ( node ) => {
2026-05-14 10:32:38 +02:00
const appConfigNode = appConfigNodes . get ( node . index ) ;
if ( appConfigNode ? . authentication ) {
2026-05-14 12:10:07 +02:00
node . authentication = node . authentication || { } ;
2026-05-14 10:32:38 +02:00
if ( appConfigNode . authentication . macaroonPath ) {
node . authentication . macaroonPath = appConfigNode . authentication . macaroonPath ;
2024-06-10 12:40:37 -07:00
}
2026-05-14 10:32:38 +02:00
if ( appConfigNode . authentication . runePath ) {
node . authentication . runePath = appConfigNode . authentication . runePath ;
2024-06-10 12:40:37 -07:00
}
2026-05-14 10:32:38 +02:00
if ( appConfigNode . authentication . lnApiPassword ) {
node . authentication . lnApiPassword = appConfigNode . authentication . lnApiPassword ;
2024-06-10 12:40:37 -07:00
}
}
} ) ;
return config ;
} ;
2023-10-02 20:55:16 -07:00
this . setSwapServerOptions = ( req ) => {
2021-12-29 18:08:41 -05:00
const swapOptions = {
2024-06-10 12:40:37 -07:00
baseUrl : req . session . selectedNode . settings . swapServerUrl ,
2023-10-02 20:55:16 -07:00
uri : '' ,
2021-12-29 18:08:41 -05:00
rejectUnauthorized : false ,
json : true ,
headers : { 'Grpc-Metadata-macaroon' : '' }
} ;
2024-06-10 12:40:37 -07:00
if ( req . session . selectedNode . authentication . swapMacaroonPath ) {
2021-12-29 18:08:41 -05:00
try {
2024-06-10 12:40:37 -07:00
swapOptions . headers = { 'Grpc-Metadata-macaroon' : fs . readFileSync ( join ( req . session . selectedNode . authentication . swapMacaroonPath , 'loop.macaroon' ) ) . toString ( 'hex' ) } ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Loop macaroon Error' , error : err } ) ;
2021-12-29 18:08:41 -05:00
}
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Swap Options Set' } ) ;
2021-12-29 18:08:41 -05:00
return swapOptions ;
} ;
this . getBoltzServerOptions = ( req ) => {
const boltzOptions = {
2024-06-10 12:40:37 -07:00
url : req . session . selectedNode . settings . boltzServerUrl ,
2021-12-29 18:08:41 -05:00
rejectUnauthorized : false ,
json : true ,
headers : { 'Grpc-Metadata-macaroon' : '' }
} ;
2024-06-10 12:40:37 -07:00
if ( req . session . selectedNode . authentication . boltzMacaroonPath ) {
2021-12-29 18:08:41 -05:00
try {
2024-06-10 12:40:37 -07:00
boltzOptions . headers = { 'Grpc-Metadata-macaroon' : fs . readFileSync ( join ( req . session . selectedNode . authentication . boltzMacaroonPath , 'admin.macaroon' ) ) . toString ( 'hex' ) } ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Boltz macaroon Error' , error : err } ) ;
2021-12-29 18:08:41 -05:00
}
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Boltz Options Set' } ) ;
2021-12-29 18:08:41 -05:00
return boltzOptions ;
} ;
this . getOptions = ( req ) => {
2024-06-10 12:40:37 -07:00
if ( req . session . selectedNode && req . session . selectedNode . authentication . options ) {
req . session . selectedNode . authentication . options . method = ( req . session . selectedNode . lnImplementation && req . session . selectedNode . lnImplementation . toUpperCase ( ) === 'LND' ) ? 'GET' : 'POST' ;
delete req . session . selectedNode . authentication . options . form ;
delete req . session . selectedNode . authentication . options . body ;
req . session . selectedNode . authentication . options . qs = { } ;
return req . session . selectedNode . authentication . options ;
2021-12-29 18:08:41 -05:00
}
2024-06-10 12:40:37 -07:00
return this . handleError ( { statusCode : 401 , message : 'Session expired after a day\'s inactivity' } , 'Session Expired' , 'Session Expiry Error' , this . selectedNode ) ;
2021-12-29 18:08:41 -05:00
} ;
this . updateSelectedNodeOptions = ( req ) => {
if ( ! req . session . selectedNode ) {
req . session . selectedNode = { } ;
}
2024-06-10 12:40:37 -07:00
req . session . selectedNode . authentication . options = {
2021-12-29 18:08:41 -05:00
url : '' ,
rejectUnauthorized : false ,
json : true ,
form : null
} ;
try {
2024-06-10 12:40:37 -07:00
if ( req . session . selectedNode && req . session . selectedNode . lnImplementation ) {
switch ( req . session . selectedNode . lnImplementation . toUpperCase ( ) ) {
2022-05-01 13:35:20 -04:00
case 'CLN' :
2023-12-05 20:32:05 -08:00
try {
2024-06-10 12:40:37 -07:00
if ( ! req . session . selectedNode . authentication . runeValue ) {
req . session . selectedNode . authentication . runeValue = this . getRuneValue ( req . session . selectedNode . authentication . runePath ) ;
2023-12-05 20:32:05 -08:00
}
2024-06-10 12:40:37 -07:00
req . session . selectedNode . authentication . options . headers = { rune : req . session . selectedNode . authentication . runeValue } ;
2023-12-05 20:32:05 -08:00
}
catch ( err ) {
throw new Error ( err ) ;
}
2021-12-29 18:08:41 -05:00
break ;
case 'ECL' :
2024-06-10 12:40:37 -07:00
req . session . selectedNode . authentication . options . headers = { authorization : 'Basic ' + Buffer . from ( ':' + req . session . selectedNode . authentication . lnApiPassword ) . toString ( 'base64' ) } ;
2021-12-29 18:08:41 -05:00
break ;
default :
2024-06-10 12:40:37 -07:00
req . session . selectedNode . authentication . options . headers = { 'Grpc-Metadata-macaroon' : fs . readFileSync ( join ( req . session . selectedNode . authentication . macaroonPath , 'admin.macaroon' ) ) . toString ( 'hex' ) } ;
2021-12-29 18:08:41 -05:00
break ;
}
}
if ( req . session . selectedNode ) {
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Updated Node Options for ' + req . session . selectedNode . lnNode } ) ;
2021-12-29 18:08:41 -05:00
}
2022-01-16 15:55:50 -05:00
return { status : 200 , message : 'Updated Successfully' } ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
req . session . selectedNode . authentication . options = {
2021-12-29 18:08:41 -05:00
url : '' ,
rejectUnauthorized : false ,
json : true ,
form : null
} ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Update Selected Node Options Error' , error : err } ) ;
2021-12-29 18:08:41 -05:00
return { status : 502 , message : err } ;
}
} ;
2023-12-05 20:32:05 -08:00
this . getRuneValue = ( rune _path ) => {
const data = fs . readFileSync ( rune _path , 'utf8' ) ;
const pattern = /LIGHTNING_RUNE="(?<runeValue>[^"]+)"/ ;
const match = data . match ( pattern ) ;
if ( match . groups . runeValue ) {
return match . groups . runeValue ;
}
else {
throw new Error ( 'Rune not found in the file.' ) ;
}
} ;
2021-12-29 18:08:41 -05:00
this . setOptions = ( req ) => {
if ( this . nodes && this . nodes . length > 0 ) {
this . nodes . forEach ( ( node ) => {
2026-05-18 12:29:15 -06:00
if ( node . authentication . options && node . authentication . options . headers ) {
return ;
}
2024-06-10 12:40:37 -07:00
node . authentication . options = {
2021-12-29 18:08:41 -05:00
url : '' ,
rejectUnauthorized : false ,
json : true ,
form : null
} ;
try {
2024-06-10 12:40:37 -07:00
if ( node . lnImplementation ) {
switch ( node . lnImplementation . toUpperCase ( ) ) {
2022-05-01 13:35:20 -04:00
case 'CLN' :
2023-12-05 20:32:05 -08:00
try {
2024-06-10 12:40:37 -07:00
if ( ! node . authentication . runeValue ) {
node . authentication . runeValue = this . getRuneValue ( node . authentication . runePath ) ;
2023-12-05 20:32:05 -08:00
}
2024-06-10 12:40:37 -07:00
node . authentication . options . headers = { rune : node . authentication . runeValue } ;
2023-12-05 20:32:05 -08:00
}
catch ( err ) {
throw new Error ( err ) ;
}
2021-12-29 18:08:41 -05:00
break ;
case 'ECL' :
2024-06-10 12:40:37 -07:00
node . authentication . options . headers = { authorization : 'Basic ' + Buffer . from ( ':' + node . authentication . lnApiPassword ) . toString ( 'base64' ) } ;
2021-12-29 18:08:41 -05:00
break ;
default :
2024-06-10 12:40:37 -07:00
node . authentication . options . headers = { 'Grpc-Metadata-macaroon' : fs . readFileSync ( join ( node . authentication . macaroonPath , 'admin.macaroon' ) ) . toString ( 'hex' ) } ;
2021-12-29 18:08:41 -05:00
break ;
}
}
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Common Set Options Error' , error : err } ) ;
node . authentication . options = {
2021-12-29 18:08:41 -05:00
url : '' ,
rejectUnauthorized : false ,
json : true ,
form : ''
} ;
}
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Set Node Options for ' + node . lnNode } ) ;
2021-12-29 18:08:41 -05:00
} ) ;
this . updateSelectedNodeOptions ( req ) ;
}
} ;
this . findNode = ( selNodeIndex ) => this . nodes . find ( ( node ) => node . index === selNodeIndex ) ;
this . replaceNode = ( req , newNode ) => {
const foundIndex = this . nodes . findIndex ( ( node ) => node . index === req . session . selectedNode . index ) ;
this . nodes . splice ( foundIndex , 1 , newNode ) ;
req . session . selectedNode = this . findNode ( req . session . selectedNode . index ) ;
} ;
this . convertTimeToEpoch = ( timeToConvert ) => Math . floor ( timeToConvert . getTime ( ) / 1000 ) ;
this . convertTimestampToTime = ( num ) => {
const myDate = new Date ( + num * 1000 ) ;
let days = myDate . getDate ( ) . toString ( ) ;
days = + days < 10 ? '0' + days : days ;
let hours = myDate . getHours ( ) . toString ( ) ;
hours = + hours < 10 ? '0' + hours : hours ;
let minutes = myDate . getMinutes ( ) . toString ( ) ;
minutes = + minutes < 10 ? '0' + minutes : minutes ;
let seconds = myDate . getSeconds ( ) . toString ( ) ;
seconds = + seconds < 10 ? '0' + seconds : seconds ;
return days + '/' + this . MONTHS [ myDate . getMonth ( ) ] . name + '/' + myDate . getFullYear ( ) + ' ' + hours + ':' + minutes + ':' + seconds ;
} ;
this . sortAscByKey = ( array , key ) => array . sort ( ( a , b ) => {
const x = + a [ key ] ;
const y = + b [ key ] ;
return ( ( x < y ) ? - 1 : ( ( x > y ) ? 1 : 0 ) ) ;
} ) ;
this . sortAscByStrKey = ( array , key ) => array . sort ( ( a , b ) => {
const x = a [ key ] ? a [ key ] . toUpperCase ( ) : '' ;
const y = b [ key ] ? b [ key ] . toUpperCase ( ) : '' ;
return ( ( x < y ) ? - 1 : ( ( x > y ) ? 1 : 0 ) ) ;
} ) ;
this . sortDescByKey = ( array , key ) => {
const temp = array . sort ( ( a , b ) => {
const x = + a [ key ] ? + a [ key ] : 0 ;
const y = + b [ key ] ? + b [ key ] : 0 ;
return ( x > y ) ? - 1 : ( ( x < y ) ? 1 : 0 ) ;
} ) ;
return temp ;
} ;
this . sortDescByStrKey = ( array , key ) => {
const temp = array . sort ( ( a , b ) => {
const x = a [ key ] ? a [ key ] . toUpperCase ( ) : '' ;
const y = b [ key ] ? b [ key ] . toUpperCase ( ) : '' ;
return ( x > y ) ? - 1 : ( ( x < y ) ? 1 : 0 ) ;
} ) ;
return temp ;
} ;
this . newestOnTop = ( array , key , value ) => {
const newlyAddedRecord = array . splice ( array . findIndex ( ( item ) => item [ key ] === value ) , 1 ) ;
2022-11-23 18:20:23 -08:00
array ? . unshift ( newlyAddedRecord [ 0 ] ) ;
2021-12-29 18:08:41 -05:00
return array ;
} ;
2022-11-23 18:20:23 -08:00
this . camelCase = ( str ) => str ? . replace ( /(?:^\w|[A-Z]|\b\w)/g , ( word , index ) => ( word . toUpperCase ( ) ) ) ? . replace ( /\s+/g , '' ) ? . replace ( /-/g , ' ' ) ;
2022-06-21 20:29:30 -04:00
this . titleCase = ( str ) => {
if ( str . indexOf ( '!\n' ) > 0 || str . indexOf ( '.\n' ) > 0 ) {
2022-11-23 18:20:23 -08:00
return str . split ( '\n' ) ? . reduce ( ( accumulator , currentStr ) => accumulator + currentStr . charAt ( 0 ) . toUpperCase ( ) + currentStr . substring ( 1 ) . toLowerCase ( ) + '\n' , '' ) ;
2022-06-21 20:29:30 -04:00
}
else {
if ( str . indexOf ( ' ' ) > 0 ) {
2022-11-23 18:20:23 -08:00
return str . split ( ' ' ) ? . reduce ( ( accumulator , currentStr ) => accumulator + currentStr . charAt ( 0 ) . toUpperCase ( ) + currentStr . substring ( 1 ) . toLowerCase ( ) + ' ' , '' ) ;
2022-06-21 20:29:30 -04:00
}
else {
return str . charAt ( 0 ) . toUpperCase ( ) + str . substring ( 1 ) . toLowerCase ( ) ;
}
}
} ;
2021-12-29 18:08:41 -05:00
this . handleError = ( errRes , fileName , errMsg , selectedNode ) => {
2024-06-10 12:40:37 -07:00
const err = JSON . parse ( JSON . stringify ( errRes ) ) ;
2021-12-29 18:08:41 -05:00
if ( ! selectedNode ) {
2024-06-10 12:40:37 -07:00
selectedNode = this . selectedNode ;
2021-12-29 18:08:41 -05:00
}
2024-06-10 12:40:37 -07:00
switch ( selectedNode . lnImplementation ) {
2021-12-29 18:08:41 -05:00
case 'LND' :
if ( err . options && err . options . headers && err . options . headers [ 'Grpc-Metadata-macaroon' ] ) {
delete err . options . headers [ 'Grpc-Metadata-macaroon' ] ;
}
if ( err . response && err . response . request && err . response . request . headers && err . response . request . headers [ 'Grpc-Metadata-macaroon' ] ) {
delete err . response . request . headers [ 'Grpc-Metadata-macaroon' ] ;
}
break ;
2022-05-01 13:35:20 -04:00
case 'CLN' :
2024-06-10 12:40:37 -07:00
if ( err . options && err . options . headers && err . options . headers . rune ) {
delete err . options . headers . rune ;
2021-12-29 18:08:41 -05:00
}
2024-06-10 12:40:37 -07:00
if ( err . response && err . response . request && err . response . request . headers && err . response . request . headers . rune ) {
delete err . response . request . headers . rune ;
2021-12-29 18:08:41 -05:00
}
break ;
case 'ECL' :
if ( err . options && err . options . headers && err . options . headers . authorization ) {
delete err . options . headers . authorization ;
}
if ( err . response && err . response . request && err . response . request . headers && err . response . request . headers . authorization ) {
delete err . response . request . headers . authorization ;
}
break ;
default :
if ( err . options && err . options . headers ) {
delete err . options . headers ;
}
break ;
}
2023-02-17 14:58:59 -08:00
this . logger . log ( { selectedNode : selectedNode , level : 'ERROR' , fileName : fileName , msg : errMsg , error : ( typeof err === 'object' ? JSON . stringify ( err ) : err ) } ) ;
2022-11-03 14:31:09 -07:00
let newErrorObj = { statusCode : 500 , message : '' , error : '' } ;
2022-11-02 15:59:37 -07:00
if ( err . code && err . code === 'ENOENT' ) {
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
// The absolute path stays in the server log above but is not echoed to clients.
2022-11-02 15:59:37 -07:00
newErrorObj = {
statusCode : 500 ,
Release 0.15.10 (#1665)
* Update version 0.15.10
* Update project dependencies to resolve Dependabot security alerts
Applies the fixes from the open Dependabot PRs (#1648, #1649, #1650) in a
single pass on the release branch, regenerating the lockfile from scratch.
axios 1.16.0 -> 1.18.1 was the only production exposure (10 advisories).
Transitive deps moved to their fixed in-range versions (fast-uri 3.1.4,
form-data, qs, tough-cookie, tar, del, globby); dev toolchain took safe
bumps (nodemon 3.1.14, eslint 9.39.5, @typescript-eslint 8.65.0).
Drops the unused protractor devDependency: no e2e directory, no config and
no e2e target in angular.json, but 100 packages and the deprecated request
stack behind it. That clears both critical advisories.
npm audit: 50 (2 critical) -> 29 (0 critical); production deps 1 -> 0.
Remaining findings are dev-only tooling needing an Angular 21 migration
rather than a version bump.
Verified: lint, 204 frontend specs, backend + frontend production builds,
and 19 API checks against the docker regtest fixture covering LND, Core
Lightning and Eclair (getinfo, channels, peers, invoices, payments and
forwarding history).
* Fill in PR number in release note (#1653)
* Harden login request validation (#1654)
Tightens server-side validation of authentication requests, guards the password-reset route behind an authenticated session, and wires the backend regression suite (test/backend/) into npm run test. Users with two-factor authentication enabled are encouraged to update promptly.
Verified: backend specs 12/12, lint green, frontend specs 204/204, and the full authentication matrix end-to-end on the docker regtest fixture.
* Reduce exposure of authentication secrets in logs and config responses (#1659)
* Reduce exposure of authentication secrets in logs and config responses
* Fill in PR number in release note (#1659)
* Harden redaction helpers and secret restore paths
* Pin deployment auth switches server-side and harden settings persistence
* Contain backup file reads and harden config persistence
* Pin backup containment root and preserve config file mode on save
* Update Angular framework packages to 20.3.27 (#1661)
* Update Angular framework packages to 20.3.27
Batches the three Dependabot PRs open against master for the Angular framework
(@angular/core #1658, @angular/compiler #1657, @angular/common #1655) into one
update on the release branch. The framework packages are pinned to exact
versions and their peer ranges require them to move together, so all nine
20.3.26 packages go to 20.3.27: animations, common, compiler, compiler-cli,
core, forms, platform-browser, platform-browser-dynamic and router.
Patch-level upstream fixes only, no advisories. The update stays inside Angular
20 - @angular/build and @angular/cli (20.3.32) and @angular/cdk/@angular/material
(20.2.14) are already at the top of their v20 lines - so it does not pull in the
Angular 21 migration tracked by #1650.
Rebuilt frontend/ for the new framework code. backend/ is unchanged, as no
server/ source moved.
* Fill in PR number in release note (#1661)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts Fixes #1630 (#1651)
* Bound remaining unbounded alias-resolution fan-outs in LND graph.ts and channels.ts
Fixes #1630
* Address review feedback: fix options race, error handling, release notes
* Improve release notes entry to cover full PR scope
* Address review feedback: per-task options copy, exclude qs from alias requests
* Stop logging the eclair auth header at DEBUG level (#1664)
* Stop logging the eclair auth header at DEBUG level
getChannels in the eclair channels controller logged its whole request
options object. Eclair authenticates with HTTP basic auth, so those
options carry the configured lnApiPassword in an authorization header -
raising an eclair node's logLevel to DEBUG wrote
"authorization":"Basic <base64>" into the node log file, which is a
recoverable form of the credential and is routinely shared when
debugging.
The log now carries only the request url and form, matching every other
DEBUG log in the controllers. This was the only site in server/ passing a
whole options object to the logger; the rest log options.form, .url,
.body or .qs, none of which hold credentials.
Present since 0.12.0 and only reachable by opting in to DEBUG (the
default log level is ERROR), but it contradicted the logging guarantee
stated for #1659.
Found by scanning node logs at DEBUG while verifying the 0.15.10 branch
against the regtest fixture. Regression test added in
test/backend/eclair-channels.test.mjs; it fails on the previous code with
"auth header key must not reach the node log".
* Fill in PR number in release note (#1664)
---------
Co-authored-by: Osuji <weezdomosuji@gmail.com>
2026-08-03 22:49:14 -07:00
message : 'No such file or directory' ,
error : 'No such file or directory'
2022-11-02 15:59:37 -07:00
} ;
}
else {
newErrorObj = {
statusCode : err . statusCode ? err . statusCode : err . status ? err . status : ( err . error && err . error . code && err . error . code === 'ECONNREFUSED' ) ? 503 : 500 ,
message : ( err . error && err . error . message ) ? err . error . message : err . message ? err . message : errMsg ,
error : ( ( err . error && err . error . error && err . error . error . error && typeof err . error . error . error === 'string' ) ? err . error . error . error :
( err . error && err . error . error && typeof err . error . error === 'string' ) ? err . error . error :
( err . error && err . error . error && err . error . error . message && typeof err . error . error . message === 'string' ) ? err . error . error . message :
( err . error && err . error . message && typeof err . error . message === 'string' ) ? err . error . message :
( err . error && typeof err . error === 'string' ) ? err . error :
( err . message && typeof err . message === 'string' ) ? err . message : ( typeof err === 'string' ) ? err : 'Unknown Error' )
} ;
}
2024-06-10 12:40:37 -07:00
if ( selectedNode . lnImplementation === 'ECL' && err . message && err . message . indexOf ( 'Authentication Error' ) < 0 && err . name && err . name === 'StatusCodeError' ) {
2022-11-16 13:02:43 -08:00
newErrorObj . statusCode = 500 ;
}
2021-12-29 18:08:41 -05:00
return newErrorObj ;
} ;
this . getRequestIP = ( req ) => ( ( typeof req . headers [ 'x-forwarded-for' ] === 'string' && req . headers [ 'x-forwarded-for' ] . split ( ',' ) . shift ( ) ) ||
req . ip ||
req . connection . remoteAddress ||
req . socket . remoteAddress ||
( req . connection . socket ? req . connection . socket . remoteAddress : null ) ) ;
this . getDummyData = ( dataKey , lnImplementation ) => {
2024-06-10 12:40:37 -07:00
const dummyDataFile = this . appConfig . rtlConfFilePath + sep + 'ECLDummyData.log' ;
2021-12-29 18:08:41 -05:00
return new Promise ( ( resolve , reject ) => {
if ( this . dummy _data _array _from _file . length === 0 ) {
fs . readFile ( dummyDataFile , 'utf8' , ( err , data ) => {
if ( err ) {
if ( err . code === 'ENOENT' ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Dummy data file does not exist' } ) ;
2021-12-29 18:08:41 -05:00
}
else {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Getting dummy data failed' } ) ;
2021-12-29 18:08:41 -05:00
}
}
else {
this . dummy _data _array _from _file = data . split ( '\n' ) ;
resolve ( this . filterData ( dataKey , lnImplementation ) ) ;
}
} ) ;
}
else {
resolve ( this . filterData ( dataKey , lnImplementation ) ) ;
}
} ) ;
} ;
2024-11-10 20:23:52 -08:00
this . readCookie = ( config ) => {
this . appConfig . SSO = config . SSO ;
2024-06-10 12:40:37 -07:00
const exists = fs . existsSync ( this . appConfig . SSO . rtlCookiePath ) ;
2021-12-29 18:08:41 -05:00
if ( exists ) {
try {
2024-06-10 12:40:37 -07:00
this . appConfig . SSO . cookieValue = fs . readFileSync ( this . appConfig . SSO . rtlCookiePath , 'utf-8' ) ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Something went wrong while reading cookie: \n' + err } ) ;
2021-12-29 18:08:41 -05:00
throw new Error ( err ) ;
}
}
else {
try {
2024-06-10 12:40:37 -07:00
const directoryName = dirname ( this . appConfig . SSO . rtlCookiePath ) ;
2021-12-29 18:08:41 -05:00
this . createDirectory ( directoryName ) ;
2024-06-10 12:40:37 -07:00
fs . writeFileSync ( this . appConfig . SSO . rtlCookiePath , crypto . randomBytes ( 64 ) . toString ( 'hex' ) ) ;
this . appConfig . SSO . cookieValue = fs . readFileSync ( this . appConfig . SSO . rtlCookiePath , 'utf-8' ) ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Something went wrong while reading the cookie: \n' + err } ) ;
2021-12-29 18:08:41 -05:00
throw new Error ( err ) ;
}
}
} ;
this . refreshCookie = ( ) => {
try {
2024-06-10 12:40:37 -07:00
fs . writeFileSync ( this . appConfig . SSO . rtlCookiePath , crypto . randomBytes ( 64 ) . toString ( 'hex' ) ) ;
this . appConfig . SSO . cookieValue = fs . readFileSync ( this . appConfig . SSO . rtlCookiePath , 'utf-8' ) ;
2021-12-29 18:08:41 -05:00
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Something went wrong while refreshing cookie' , error : err } ) ;
2021-12-29 18:08:41 -05:00
throw new Error ( err ) ;
}
} ;
this . createDirectory = ( directoryName ) => {
const initDir = isAbsolute ( directoryName ) ? sep : '' ;
2022-11-23 18:20:23 -08:00
directoryName . split ( sep ) ? . reduce ( ( parentDir , childDir ) => {
2021-12-29 18:08:41 -05:00
const curDir = resolve ( parentDir , childDir ) ;
try {
if ( ! fs . existsSync ( curDir ) ) {
fs . mkdirSync ( curDir ) ;
}
}
catch ( err ) {
if ( err . code !== 'EEXIST' ) {
if ( err . code === 'ENOENT' ) {
throw new Error ( ` ENOENT: No such file or directory, mkdir ' ${ directoryName } '. Ensure that the path separator is ' ${ sep } ' ` ) ;
}
else {
throw err ;
}
}
}
return curDir ;
} , initDir ) ;
} ;
this . replacePasswordWithHash = ( multiPassHashed ) => {
2024-06-10 12:40:37 -07:00
this . appConfig . rtlConfFilePath = process . env . RTL _CONFIG _PATH ? process . env . RTL _CONFIG _PATH : join ( dirname ( fileURLToPath ( import . meta . url ) ) , '../..' ) ;
2021-12-29 18:08:41 -05:00
try {
2024-06-10 12:40:37 -07:00
const RTLConfFile = this . appConfig . rtlConfFilePath + sep + 'RTL-Config.json' ;
2021-12-29 18:08:41 -05:00
const config = JSON . parse ( fs . readFileSync ( RTLConfFile , 'utf-8' ) ) ;
config . multiPassHashed = multiPassHashed ;
delete config . multiPass ;
fs . writeFileSync ( RTLConfFile , JSON . stringify ( config , null , 2 ) , 'utf-8' ) ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' } ) ;
2021-12-29 18:08:41 -05:00
return config . multiPassHashed ;
}
catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Password hashing failed' , error : err } ) ;
2021-12-29 18:08:41 -05:00
}
} ;
this . getAllNodeAllChannelBackup = ( node ) => {
2024-06-10 12:40:37 -07:00
const channel _backup _file = node . settings . channelBackupPath + sep + 'channel-all.bak' ;
2021-12-29 18:08:41 -05:00
const options = {
2024-06-10 12:40:37 -07:00
url : node . settings . lnServerUrl + '/v1/channels/backup' ,
2021-12-29 18:08:41 -05:00
rejectUnauthorized : false ,
json : true ,
2024-06-10 12:40:37 -07:00
headers : { 'Grpc-Metadata-macaroon' : fs . readFileSync ( node . authentication . macaroonPath + '/admin.macaroon' ) . toString ( 'hex' ) }
2021-12-29 18:08:41 -05:00
} ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Getting Channel Backup for Node ' + node . lnNode + '..' } ) ;
2021-12-29 18:08:41 -05:00
request ( options ) . then ( ( body ) => {
fs . writeFile ( channel _backup _file , JSON . stringify ( body ) , ( err ) => {
if ( err ) {
2024-06-10 12:40:37 -07:00
if ( node . lnNode ) {
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Error in Channel Backup for Node ' + node . lnNode , error : err } ) ;
2021-12-29 18:08:41 -05:00
}
else {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Error in Channel Backup for File ' + channel _backup _file , error : err } ) ;
2021-12-29 18:08:41 -05:00
}
}
else {
2024-06-10 12:40:37 -07:00
if ( node . lnNode ) {
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Successful in Channel Backup for Node ' + node . lnNode , data : body } ) ;
2021-12-29 18:08:41 -05:00
}
else {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Successful in Channel Backup for File ' + channel _backup _file , data : body } ) ;
2021-12-29 18:08:41 -05:00
}
}
} ) ;
} , ( err ) => {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Error in Channel Backup for Node ' + node . lnNode , error : err } ) ;
2022-01-16 15:55:50 -05:00
fs . writeFile ( channel _backup _file , '' , ( ) => { } ) ;
2021-12-29 18:08:41 -05:00
} ) ;
} ;
this . isVersionCompatible = ( currentVersion , checkVersion ) => {
2023-12-05 20:32:05 -08:00
if ( currentVersion && currentVersion !== '' ) {
// eslint-disable-next-line prefer-named-capture-group
const pattern = /v?(\d+(\.\d+)*)/ ;
const match = currentVersion . match ( pattern ) ;
if ( match && match . length && match . length > 1 ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Global Version ' + match [ 1 ] } ) ;
this . logger . log ( { selectedNode : this . selectedNode , level : 'INFO' , fileName : 'Common' , msg : 'Checking Compatiblility with Version ' + checkVersion } ) ;
2023-12-05 20:32:05 -08:00
const currentVersionArr = match [ 1 ] . split ( '.' ) || [ ] ;
currentVersionArr [ 1 ] = currentVersionArr [ 1 ] . substring ( 0 , 2 ) ;
const checkVersionsArr = checkVersion . split ( '.' ) ;
checkVersionsArr [ 1 ] = checkVersionsArr [ 1 ] . substring ( 0 , 2 ) ;
return ( + currentVersionArr [ 0 ] > + checkVersionsArr [ 0 ] ) ||
( + currentVersionArr [ 0 ] === + checkVersionsArr [ 0 ] && + currentVersionArr [ 1 ] > + checkVersionsArr [ 1 ] ) ||
( + currentVersionArr [ 0 ] === + checkVersionsArr [ 0 ] && + currentVersionArr [ 1 ] === + checkVersionsArr [ 1 ] && + currentVersionArr [ 2 ] >= + checkVersionsArr [ 2 ] ) ;
}
else {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this . selectedNode , level : 'ERROR' , fileName : 'Common' , msg : 'Invalid Version String ' + currentVersion } ) ;
2023-12-05 20:32:05 -08:00
return false ;
}
2021-12-29 18:08:41 -05:00
}
return false ;
} ;
this . getMonthDays = ( selMonth , selYear ) => ( ( selMonth === 1 && selYear % 4 === 0 ) ? ( this . MONTHS [ selMonth ] . days + 1 ) : this . MONTHS [ selMonth ] . days ) ;
this . logEnvVariables = ( req ) => {
const selNode = req . session . selectedNode ;
if ( selNode && selNode . index ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : selNode , level : 'INFO' , fileName : 'Config Setup:' , msg : JSON . stringify ( this . removeSecureData ( JSON . parse ( JSON . stringify ( this . appConfig ) ) ) ) } ) ;
2024-11-18 20:27:51 -08:00
this . logger . log ( { selectedNode : selNode , level : 'INFO' , fileName : 'Config Setup Variable' , msg : 'SSO: ' + this . appConfig . SSO . rtlSSO } ) ;
2021-12-29 18:08:41 -05:00
}
} ;
this . filterData = ( dataKey , lnImplementation ) => {
let search _string = '' ;
if ( lnImplementation === 'ECL' ) {
switch ( dataKey ) {
case 'GetInfo' :
search _string = 'INFO: GetInfo => Get Info Response: ' ;
break ;
case 'Fees' :
search _string = 'INFO: Fees => Fee Response: ' ;
break ;
case 'Payments' :
search _string = 'INFO: Fees => Payments Response: ' ;
break ;
case 'Invoices' :
search _string = 'INFO: Invoice => Invoices List Received: ' ;
break ;
case 'OnChainBalance' :
search _string = 'INFO: Onchain => Balance Received: ' ;
break ;
case 'Peers' :
search _string = 'INFO: Peers => Peers with Alias: ' ;
break ;
case 'Channels' :
search _string = 'INFO: Channels => Simplified Channels with Alias: ' ;
break ;
default :
search _string = 'Random Line' ;
break ;
}
}
2022-05-01 13:35:20 -04:00
else if ( lnImplementation === 'CLN' ) {
2021-12-29 18:08:41 -05:00
switch ( dataKey ) {
case 'GetInfo' :
search _string = 'DEBUG: GetInfo => Node Information. ' ;
break ;
case 'Fees' :
search _string = 'DEBUG: Fees => Fee Received. ' ;
break ;
case 'Payments' :
search _string = 'DEBUG: Payments => Payment List Received: ' ;
break ;
case 'Invoices' :
search _string = 'DEBUG: Invoice => Invoices List Received. ' ;
break ;
case 'ChannelBalance' :
search _string = 'DEBUG: Channels => Local Remote Balance. ' ;
break ;
case 'Peers' :
search _string = 'DEBUG: Peers => Peers with Alias: ' ;
break ;
case 'Channels' :
search _string = 'DEBUG: Channels => List Channels: ' ;
break ;
case 'Balance' :
search _string = 'DEBUG: Balance => Balance Received. ' ;
break ;
case 'ForwardingHistory' :
search _string = 'DEBUG: Channels => Forwarding History Received: ' ;
break ;
case 'UTXOs' :
search _string = 'DEBUG: OnChain => List Funds Received. ' ;
break ;
case 'FeeRateperkb' :
search _string = 'DEBUG: Network => Network Fee Rates Received for perkb. ' ;
break ;
case 'FeeRateperkw' :
search _string = 'DEBUG: Network => Network Fee Rates Received for perkw. ' ;
break ;
default :
search _string = 'Random Line' ;
break ;
}
}
const foundDataLine = this . dummy _data _array _from _file . find ( ( dataItem ) => dataItem . includes ( search _string ) ) ;
const dataStr = foundDataLine ? foundDataLine . substring ( ( foundDataLine . indexOf ( search _string ) ) + search _string . length ) : '{}' ;
return JSON . parse ( dataStr ) ;
} ;
2025-09-09 14:48:23 +05:30
this . runWithConcurrencyLimit = ( tasks , limit , done ) => {
2026-07-17 21:56:15 -07:00
const results = new Array ( tasks ? . length || 0 ) ;
2026-07-17 22:50:54 -07:00
// 'done' must fire exactly once. Guard it: multiple runNext() completions (e.g. several
// non-function task elements draining synchronously) must not send the response twice.
let finished = false ;
const finish = ( ) => {
if ( finished ) {
return ;
}
finished = true ;
done ( results ) ;
} ;
2026-07-17 21:56:15 -07:00
// No tasks: the start loop below never runs, so 'done' would never fire and the
// response would hang. Resolve immediately for empty lists (e.g. a node with no peers).
if ( ! tasks || tasks . length === 0 ) {
2026-07-17 22:50:54 -07:00
return finish ( ) ;
2026-07-17 21:56:15 -07:00
}
2025-09-09 14:48:23 +05:30
let nextIndex = 0 ;
let activeCount = 0 ;
const runNext = ( ) => {
if ( nextIndex >= tasks . length ) {
if ( activeCount === 0 ) {
2026-07-17 22:50:54 -07:00
finish ( ) ; // all tasks are finished
2025-09-09 14:48:23 +05:30
}
return ;
}
const currentIndex = nextIndex ++ ;
activeCount ++ ;
const task = tasks [ currentIndex ] ;
if ( typeof task !== 'function' ) {
results [ currentIndex ] = { error : new Error ( 'Invalid task at index ' + currentIndex ) } ;
activeCount -- ;
runNext ( ) ;
return ;
}
Promise . resolve ( ) . then ( ( ) => task ( ) ) . then ( ( result ) => {
results [ currentIndex ] = result ;
} ) . catch ( ( err ) => {
results [ currentIndex ] = { error : err } ;
} ) . finally ( ( ) => {
activeCount -- ;
runNext ( ) ;
} ) ;
} ;
2026-07-17 22:21:38 -07:00
// Normalize to at least 1: a non-positive limit would start no tasks, so 'done' (only
// reached from a task's finally) would never fire and the response would hang.
const startCount = Math . max ( 1 , limit ) ;
for ( let i = 0 ; i < startCount && i < tasks . length ; i ++ ) {
2025-09-09 14:48:23 +05:30
runNext ( ) ;
}
} ;
2021-12-29 18:08:41 -05:00
}
}
export const Common = new CommonService ( ) ;