diff --git a/README.md b/README.md index 84b1eb2..6a7e430 100644 --- a/README.md +++ b/README.md @@ -74,4 +74,8 @@ curl -X PUT http:///admin/users//account-uuid -d '{"admin_password": "", "account_uuid": ""}' ``` +### Failures around Lightning Network payments +Ensure you are running Node.js v18.x. Preferably, use the provided nix-shell environment to ensure you are using the correct version of Node.js. + +There is a known issue with Node.js v22.x where the `ln.connect_and_init` call does not work with the way things are setup. diff --git a/package.json b/package.json index 07dd73e..d528c3f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "start": "node src/index.js", "dev": "TRANSLATION_PROVIDER=mock ENABLE_HTTP_AUTH=\"true\" node src/index.js", "dev-debug": "TRANSLATION_PROVIDER=mock ENABLE_HTTP_AUTH=\"true\" node --inspect src/index.js", + "dev-debug-cli": "TRANSLATION_PROVIDER=mock ENABLE_HTTP_AUTH=\"true\" node inspect src/index.js", "type-check": "tsc --checkJs --allowJs src/*.js --noEmit --skipLibCheck", "type-check-path": "tsc --checkJs --allowJs --noEmit --skipLibCheck" }, diff --git a/src/router_config.js b/src/router_config.js index bcb7f89..22c531c 100644 --- a/src/router_config.js +++ b/src/router_config.js @@ -305,7 +305,87 @@ function config_router(app) { } }) - // MARK: OTP routes + // MARK: OTP lightning checkout routes + + /** + * This route is used to request an OTP code for a specific checkout. + * + * @param {string} req.params.checkout_id - The ID of the checkout object. + * @param {string} req.params.pubkey - The public key of the user verifying the OTP. + * + * @returns {Object} - A JSON response indicating whether the OTP code was sent successfully + */ + router.post('/ln-checkout/:checkout_id/request-otp/:pubkey', async (req, res) => { + const pubkey = req.params.pubkey + if (!pubkey) { + invalid_request(res, 'Could not parse account pubkey') + return + } + + const checkout_id = req.params.checkout_id + if (!checkout_id) { + error_response(res, 'Could not parse checkout_id') + return + } + + const otp_code = await app.web_auth_manager.generate_otp(pubkey) + await app.web_auth_manager.send_otp(pubkey, otp_code) + json_response(res, { success: true }) + }); + + /** + * This route is used to verify an OTP code for a specific checkout. + * + * @param {string} req.params.checkout_id - The ID of the checkout object. + * @param {string} req.params.pubkey - The public key of the user verifying the OTP. + * @param {string} req.body.otp_code - The OTP code to verify. + * + * @returns {Object} - A JSON response containing the checkout object if the OTP is valid, or an error message if invalid. + */ + router.put('/ln-checkout/:checkout_id/verify-otp/:pubkey', async (req, res) => { + const pubkey = req.params.pubkey + if (!pubkey) { + invalid_request(res, 'Could not parse account pubkey') + return + } + + const checkout_id = req.params.checkout_id + if (!checkout_id) { + error_response(res, 'Could not parse checkout_id') + return + } + + const otp_code = req.body.otp_code + if (!otp_code) { + invalid_request(res, 'Missing otp_code') + return + } + + const is_valid = await app.web_auth_manager.validate_otp(pubkey, otp_code) + if(!is_valid) { + unauthorized_response(res, { valid: false }) + return + } + + try { + const response = await app.invoice_manager.verify_checkout_object(checkout_id, pubkey) + if (response.request_error) { + invalid_request(res, response.request_error) + } + if (response.checkout_object) { + const session_token = await app.web_auth_manager.create_session(pubkey) + json_response(res, { + checkout_object: response.checkout_object, + otp: { valid: true, session_token: session_token } + }) + } + } catch (e) { + error("%s", e.toString()) + invalid_request(res, e.toString()) + } + }) + + // MARK: OTP login routes router.post('/accounts/:pubkey/request-otp', async (req, res) => { const pubkey = req.params.pubkey diff --git a/src/web_auth.js b/src/web_auth.js index 8ab6661..552c554 100644 --- a/src/web_auth.js +++ b/src/web_auth.js @@ -137,31 +137,31 @@ class WebAuthManager { async require_web_auth(req, res, next) { const auth_header = req.header('Authorization'); if (!auth_header) { - unauthorized_response(res, 'Unauthorized'); + unauthorized_response(res, 'Unauthorized, no auth header'); return; } const [auth_type, token] = auth_header.split(' '); if (auth_type !== 'Bearer') { - unauthorized_response(res, 'Unauthorized'); + unauthorized_response(res, 'Unauthorized, invalid auth type'); return; } if (!token) { - unauthorized_response(res, 'Unauthorized'); + unauthorized_response(res, 'Unauthorized, no token'); return; } const session_data = await this.dbs.sessions.get(token); if (!session_data) { - unauthorized_response(res, 'Unauthorized'); + unauthorized_response(res, 'Unauthorized, invalid token'); return; } // Check if the session has expired if (current_time() - session_data.created_at > this.session_expiry) { await this.dbs.sessions.del(token); - unauthorized_response(res, 'Unauthorized'); + unauthorized_response(res, 'Unauthorized, session expired'); return; }