Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 43 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,36 +52,53 @@
This application accepts & depends upon these variables to be passed through environment:

```
- SINGLE_SIGN_ON: Flag to bypass application level authentication (valid values: true/false, default: false)
- LOCAL_HOST: Device url for connect url links (optional; for connect wallet screen)
- DEVICE_DOMAIN_NAME: Device name/IP for lnmessage connect url feature (optional; for connect wallet screen)
- BITCOIN_NODE_IP: IP address of bitcoin node container (required)
- BITCOIN_NETWORK: Bitcoin network type (optional; for entrypoint.sh; valid values: bitcoin/signet/testnet/regtest)
- APP_CONFIG_DIR: Path for cln-application's configuration file (required; config.json)
- APP_MODE: Mode for logging and other settings (valid values: production/development/testing, default: production)
- APP_CONNECT: Choose how to connect to CLN (valid values: COMMANDO/REST/GRPC, default: COMMANDO)
- APP_PROTOCOL: Protocol on which the application will be served (valid values: http/https, default: http)
- APP_IP: IP address of this application (cln-application) container (required)
- APP_PORT: Port on which this application should be served (required)
- LIGHTNING_IP: IP address of Core lightning node container (required)
- LIGHTNING_PATH: Path for core lightning (optional; required for entrypoint.sh)
- HIDDEN_SERVICE_URL: REST hidden service url (optional; for connect wallet screen; Used for Tor Domain also)
- COMMANDO_CONFIG: Full Path including file name for commando auth with PUBKEY & RUNE (required)
- LIGHTNING_WEBSOCKET_PROTOCOL: Core lightning's web socket or web socket proxy (valid values: ws/wss, default: ws)
- LIGHTNING_WEBSOCKET_PORT: Core lightning's websocket port (required with default APP_CONNECT; from cln's config.json; starting with `bind-addr=ws:`/`wss-bind-addr`)
- LIGHTNING_REST_PROTOCOL: Protocol on which REST is served (valid values: http/https, default: https)
- LIGHTNING_REST_PORT: REST server port (required if APP_CONNECT is REST)
- LIGHTNING_CERTS_DIR: Path for core lightning certificates (Required the PROTOCOL is 'https/wss')
- LIGHTNING_GRPC_PROTOCOL: Core lightning's GRPC protocol (valid values: http/https, default: http)
- LIGHTNING_GRPC_PORT: Core lightning's GRPC port (Required if APP_CONNECT is GRPC)
- BITCOIN_HOST: Hostname/IP address of bitcoin node container (configurable to run lightningd with `--bitcoin-rpcconnect`, default: `localhost`)
- BITCOIN_NETWORK: Bitcoin network type (for entrypoint.sh and lightningd with `--network`; valid values: bitcoin/signet/testnet/regtest; default: `bitcoin`)

- APP_SINGLE_SIGN_ON: Flag to bypass application level authentication (valid values: true/false, default: false)
- APP_PROTOCOL: Protocol on which the application will be served (valid values: http/https, default: `http`)
- APP_HOST: Hostname/IP address of cln-application's container (default: `localhost`)
- APP_PORT: Port on which this application should be served (default: `2103`)

- APP_CONFIG_FILE: Path for cln-application's configuration file (default: `./config.json`)
- APP_LOG_FILE: Path for cln-application's log file (default: `./application-cln.log`)
- APP_MODE: Mode for logging and other settings (valid values: production/development/testing, default: `production`)
- APP_CONNECT: Choose how to connect to CLN (valid values: COMMANDO/REST/GRPC, default: `COMMANDO`)

- LIGHTNING_DATA_DIR: Path for core lightning (used by entrypoint.sh, default: ``)
- LIGHTNING_HOST: IP address of Core lightning node container (used for `COMMANDO` APP_CONNECT, default: `localhost`)
- LIGHTNING_TOR_HOST: REST hidden service url (default: ``)

- LIGHTNING_VARS_FILE: Full Path including the file name for connection auth with LIGHTNING_PUBKEY & LIGHTNING_RUNE (defult: `./.commando-env`)
- LIGHTNING_WS_PROTOCOL: Core lightning's web socket is serving on ws or serving via WSSProxy (valid values: ws/wss, default: `ws`)
- LIGHTNING_WS_PORT: Core lightning's websocket port (used by `COMMANDO` APP_CONNECT; with `bind-addr=ws:`/`wss-bind-addr` in CLN config; default: `5001`)
- LIGHTNING_WS_CLIENT_KEY_FILE: Client key file path including file name for websocket TLS authentication (used by `COMMANDO` APP_CONNECT and `wss` LIGHTNING_WS_PROTOCOL; default: `./client-key.pem`)
- LIGHTNING_WS_CLIENT_CERT_FILE: Client certificate file path including file name for websocket TLS authentication (used by `COMMANDO` APP_CONNECT and `wss` LIGHTNING_WS_PROTOCOL; default: `./client.pem`)
- LIGHTNING_WS_CA_CERT_FILE: CA certificate file path including file name for websocket TLS authentication (default: `./ca.pem`)

- LIGHTNING_REST_PROTOCOL: Protocol on which REST is served (valid values: http/https, default: `https`)
- LIGHTNING_REST_HOST: IP address/hostname of Core Lightning REST interface (used if APP_CONNECT is `REST`, default: `localhost`)
- LIGHTNING_REST_TOR_HOST: Tor hidden service URL for Core Lightning REST interface (default: ``)
- LIGHTNING_REST_PORT: REST server port (used if APP_CONNECT is `REST`; default: `3010`)
- LIGHTNING_REST_CLIENT_KEY_FILE: Client key file path including file name for REST TLS authentication (default: `./client-key.pem`)
- LIGHTNING_REST_CLIENT_CERT_FILE: Client certificate file path including file name for REST TLS authentication (default: `./client.pem`)
- LIGHTNING_REST_CA_CERT_FILE: CA certificate file path including file name for REST TLS authentication (used by `REST` APP_CONNECT and `https` LIGHTNING_REST_PROTOCOL; default: `./ca.pem`)

- LIGHTNING_GRPC_HOST: IP address/hostname of Core Lightning GRPC interface (used if APP_CONNECT is `GRPC`, default: `localhost`)
- LIGHTNING_GRPC_TOR_HOST: Tor hidden service URL for Core Lightning GRPC interface (default: ``)
- LIGHTNING_GRPC_PORT: Core lightning's GRPC port (used if APP_CONNECT is `GRPC`; default: `9736`)
- LIGHTNING_GRPC_PROTO_PATH: URL to directory containing CLN gRPC protocol definitions (default: `https://github.com/ElementsProject/lightning/tree/master/cln-grpc/proto`)
- LIGHTNING_GRPC_CLIENT_KEY_FILE: Client key file path including file name for GRPC TLS authentication (used by `GRPC` APP_CONNECT; default: `./client-key.pem`)
- LIGHTNING_GRPC_CLIENT_CERT_FILE: Client certificate file path including file name for GRPC TLS authentication (used by `GRPC` APP_CONNECT; default: `./client.pem`)
- LIGHTNING_GRPC_CA_CERT_FILE: CA certificate file path including file name for GRPC TLS authentication (used by `GRPC` APP_CONNECT; default: `./ca.pem`)
```

Set these variables either via terminal OR by env.sh script OR by explicitly loading variables from .env files.
Important Note: Environment variables take precedence over config.json variables. Like `SINGLE_SIGN_ON` will take higher precedence over
Important Note: Environment variables take precedence over config.json variables. Like `APP_SINGLE_SIGN_ON` will take higher precedence over
`singleSignOn` from config.json.

- ### Application Configuration
This is the config.json file which is required by application's frontend. If the file named `config.json` is missing at `APP_CONFIG_DIR` location, one like below will be auto created:
This is the default `config.json` file which is required by application's frontend. If the file `APP_CONFIG_FILE` is missing at the location, one like below will be auto created:

```
{
Expand All @@ -97,8 +114,8 @@

- ### Commando Authentication
- This application utilizes [lnmessage](https://github.com/aaronbarnardsound/lnmessage) and [commando](https://docs.corelightning.org/reference/lightning-commando) for connecting with core lightning node. The connection is trustless and end-to-end encrypted. Commando manages authentication and authorization through runes, which can grant either full or fine-grained permissions.
- The backend server reads `LIGHTNING_PUBKEY` & `LIGHTNING_RUNE` from the `COMMANDO_CONFIG` file for this communication.
- Values can either be set manually or script `entrypoint.sh` can be used to call `getinfo` and `createrune` methods and save values in `COMMANDO_CONFIG`.
- The backend server reads `LIGHTNING_PUBKEY` & `LIGHTNING_RUNE` from the `LIGHTNING_VARS_FILE` file for this communication.
- Values can either be set manually or script `entrypoint.sh` can be used to call `getinfo` and `createrune` methods and save values in `LIGHTNING_VARS_FILE`.
- `entrypoint.sh` can only run for the locally installed lightning. If `cln-application` is running remotely then pubkey and
rune can be set manually.
- Sample commando config should look like:
Expand Down
24 changes: 11 additions & 13 deletions apps/backend/source/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import handleError from '../shared/error-handler.js';
import { verifyPassword, isAuthenticated, isValidPassword } from '../shared/utils.js';
import { AuthError } from '../models/errors.js';

class AuthController {
userLogin(req: Request, res: Response, next: NextFunction) {
export class AuthController {
userLogin = async (req: Request, res: Response, next: NextFunction) => {
logger.info('Logging in');
try {
const vpRes = verifyPassword(req.body.password);
Expand All @@ -25,19 +25,19 @@ class AuthController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

userLogout(req: Request, res: Response, next: NextFunction) {
userLogout = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Logging out');
res.clearCookie('token');
res.status(201).json({ isAuthenticated: false, isValidPassword: isValidPassword() });
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

resetPassword(req: Request, res: Response, next: NextFunction) {
resetPassword = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Resetting password');
const isValid = req.body.isValid;
Expand Down Expand Up @@ -76,14 +76,14 @@ class AuthController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

isUserAuthenticated(req: Request, res: Response, next: NextFunction) {
isUserAuthenticated = async (req: Request, res: Response, next: NextFunction) => {
try {
const uaRes = isAuthenticated(req.cookies.token);
if (req.body.returnResponse) {
// Frontend is asking if user is authenticated or not
if (APP_CONSTANTS.SINGLE_SIGN_ON === 'true') {
if (APP_CONSTANTS.APP_SINGLE_SIGN_ON === 'true') {
return res.status(201).json({ isAuthenticated: true, isValidPassword: true });
} else {
const vpRes = isValidPassword();
Expand All @@ -99,7 +99,7 @@ class AuthController {
}
} else {
// Backend APIs are asking if user is authenticated or not
if (uaRes === true || APP_CONSTANTS.SINGLE_SIGN_ON === 'true') {
if (uaRes === true || APP_CONSTANTS.APP_SINGLE_SIGN_ON === 'true') {
return next();
} else {
return res.status(401).json({ error: 'Unauthorized user' });
Expand All @@ -108,7 +108,5 @@ class AuthController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};
}

export default new AuthController();
20 changes: 11 additions & 9 deletions apps/backend/source/controllers/lightning.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Request, Response, NextFunction } from 'express';
import handleError from '../shared/error-handler.js';
import { CLNService, LightningService } from '../service/lightning.service.js';
import { LightningService } from '../service/lightning.service.js';
import { logger } from '../shared/logger.js';
import { AppConnect, APP_CONSTANTS } from '../shared/consts.js';

const clnService: LightningService = CLNService;
export class LightningController {
private clnService: LightningService;

class LightningController {
callMethod(req: Request, res: Response, next: NextFunction) {
constructor(clnService: LightningService) {
this.clnService = clnService;
}

callMethod = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Calling method: ' + req.body.method);
clnService
this.clnService
.call(req.body.method, req.body.params)
.then((commandRes: any) => {
logger.info(
Expand All @@ -25,7 +29,7 @@ class LightningController {
req.body.method === 'listpeers'
) {
// Filter out ln message pubkey from peers list
const lnmPubkey = clnService.getLNMsgPubkey();
const lnmPubkey = this.clnService.getLNMsgPubkey();
commandRes.peers = commandRes.peers.filter((peer: any) => peer.id !== lnmPubkey);
res.status(200).json(commandRes);
} else {
Expand All @@ -44,7 +48,5 @@ class LightningController {
} catch (error: any) {
return handleError(error, req, res, next);
}
}
};
}

export default new LightningController();
45 changes: 26 additions & 19 deletions apps/backend/source/controllers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,24 @@ import {
import { logger } from '../shared/logger.js';
import handleError from '../shared/error-handler.js';
import { APIError } from '../models/errors.js';
import { addServerConfig, refreshEnvVariables } from '../shared/utils.js';
import { CLNService } from '../service/lightning.service.js';
import { addServerConfig, setEnvVariables } from '../shared/utils.js';
import { ShowRunes } from '../models/showrunes.type.js';
import { LightningService } from '../service/lightning.service.js';

class SharedController {
getApplicationSettings(req: Request, res: Response, next: NextFunction) {
export class SharedController {
private clnService: LightningService;

constructor(clnService: LightningService) {
this.clnService = clnService;
}

getApplicationSettings = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Getting Application Settings from ' + APP_CONSTANTS.APP_CONFIG_FILE);
if (!fs.existsSync(APP_CONSTANTS.APP_CONFIG_FILE)) {
logger.warning(
`Config file ${APP_CONSTANTS.APP_CONFIG_FILE} not found. Creating default config.`,
);
fs.writeFileSync(
APP_CONSTANTS.APP_CONFIG_FILE,
JSON.stringify(DEFAULT_CONFIG, null, 2),
Expand All @@ -39,9 +48,9 @@ class SharedController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

setApplicationSettings(req: Request, res: Response, next: NextFunction) {
setApplicationSettings = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Updating Application Settings: ' + JSON.stringify(req.body));
const config = JSON.parse(fs.readFileSync(APP_CONSTANTS.APP_CONFIG_FILE, 'utf-8'));
Expand All @@ -55,19 +64,19 @@ class SharedController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

getWalletConnectSettings(req: Request, res: Response, next: NextFunction) {
getWalletConnectSettings = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Getting Connection Settings');
refreshEnvVariables();
setEnvVariables();
res.status(200).json(APP_CONSTANTS);
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

getFiatRate(req: Request, res: Response, next: NextFunction) {
getFiatRate = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Getting Fiat Rate for: ' + req.params.fiatCurrency);
const FIAT_VENUE = FIAT_VENUES.hasOwnProperty(req.params.fiatCurrency)
Expand All @@ -94,12 +103,12 @@ class SharedController {
} catch (error: any) {
handleError(error, req, res, next);
}
}
};

async saveInvoiceRune(req: Request, res: Response, next: NextFunction) {
saveInvoiceRune = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.info('Saving Invoice Rune');
const showRunes: ShowRunes = await CLNService.call('showrunes', []);
const showRunes: ShowRunes = await this.clnService.call('showrunes', []);
const invoiceRune = showRunes.runes.find(
rune =>
rune.restrictions.some(restriction =>
Expand All @@ -109,17 +118,15 @@ class SharedController {
restriction.alternatives.some(alternative => alternative.value === 'listinvoices'),
),
);
if (invoiceRune && fs.existsSync(APP_CONSTANTS.COMMANDO_CONFIG)) {
if (invoiceRune && fs.existsSync(APP_CONSTANTS.LIGHTNING_VARS_FILE)) {
const invoiceRuneString = `INVOICE_RUNE="${invoiceRune.rune}"\n`;
fs.appendFileSync(APP_CONSTANTS.COMMANDO_CONFIG, invoiceRuneString, 'utf-8');
fs.appendFileSync(APP_CONSTANTS.LIGHTNING_VARS_FILE, invoiceRuneString, 'utf-8');
res.status(201).send();
} else {
throw new Error('Invoice rune not found or .commando-env does not exist.');
}
} catch (error: any) {
handleError(error, req, res, next);
}
}
};
}

export default new SharedController();
11 changes: 6 additions & 5 deletions apps/backend/source/routes/v1/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import express from 'express';
import { CommonRoutesConfig } from '../../shared/routes.config.js';
import AuthController from '../../controllers/auth.js';
import { AuthController } from '../../controllers/auth.js';
import { API_VERSION } from '../../shared/consts.js';

const AUTH_ROUTE = '/auth';
Expand All @@ -11,12 +11,13 @@ export class AuthRoutes extends CommonRoutesConfig {
}

configureRoutes() {
this.app.route(API_VERSION + AUTH_ROUTE + '/logout/').get(AuthController.userLogout);
this.app.route(API_VERSION + AUTH_ROUTE + '/login/').post(AuthController.userLogin);
this.app.route(API_VERSION + AUTH_ROUTE + '/reset/').post(AuthController.resetPassword);
const authController = new AuthController();
this.app.route(API_VERSION + AUTH_ROUTE + '/logout/').get(authController.userLogout);
this.app.route(API_VERSION + AUTH_ROUTE + '/login/').post(authController.userLogin);
this.app.route(API_VERSION + AUTH_ROUTE + '/reset/').post(authController.resetPassword);
this.app
.route(API_VERSION + AUTH_ROUTE + '/isauthenticated/')
.post(AuthController.isUserAuthenticated);
.post(authController.isUserAuthenticated);
return this.app;
}
}
15 changes: 11 additions & 4 deletions apps/backend/source/routes/v1/lightning.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import express from 'express';
import { CommonRoutesConfig } from '../../shared/routes.config.js';
import AuthController from '../../controllers/auth.js';
import LightningController from '../../controllers/lightning.js';
import { AuthController } from '../../controllers/auth.js';
import { LightningController } from '../../controllers/lightning.js';
import { API_VERSION } from '../../shared/consts.js';
import { LightningService } from '../../service/lightning.service.js';

const LIGHTNING_ROOT_ROUTE = '/cln';

export class LightningRoutes extends CommonRoutesConfig {
constructor(app: express.Application) {
private clnService: LightningService;

constructor(app: express.Application, clnService: LightningService) {
super(app, 'Lightning Routes');
this.clnService = clnService;
}

configureRoutes() {
const authController = new AuthController();
const lightningController = new LightningController(this.clnService);
this.app
.route(API_VERSION + LIGHTNING_ROOT_ROUTE + '/call')
.post(AuthController.isUserAuthenticated, LightningController.callMethod);
.post(authController.isUserAuthenticated, lightningController.callMethod);

return this.app;
}
}
Loading