diff --git a/README.md b/README.md index a3ee82f1f..9d91d0c3f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Mifos X Web App is a modern single-page application (SPA) built on top of the Mi ## Quick Links -- [Live Demo](https://sandbox.mifos.community/#/login) (Updated nightly ** System is restored every 6 hours **) +- [Live Demo](https://sandbox.mifos.community/#/login) (Updated nightly — sandbox data is reset every 6 hours; test data and transient state may be cleared.) - [GitHub Repository](https://github.com/openMF/web-app) - [Slack Channel](https://app.slack.com/client/T0F5GHE8Y/CJJGJLN10) - [Jira Board of Mifos](https://mifosforge.jira.com/jira/your-work) @@ -31,13 +31,13 @@ Before installing the web app, you need to set up the Fineract backend server: 1. **Choose ONE of these backend options:** - **Option A: Use existing remote server** - - Use the sandbox (MariaDB) at https://sandbox.mifos.community ** System is restored every 6 hours ** - - Use the demo (MariaDB) at https://demo.mifos.community - - Use the demo (Keycloak) at https://oauth.mifos.community - - Use the demo (2FA) at https://2fa.mifos.community - - Use the demo (Oidc) at https://oidc.mifos.community - - Use the demo (Postgres) at https://elephant.mifos.community - - Configure to your server by updating API URLs in environment files + - Use the [sandbox (MariaDB)](https://sandbox.mifos.community) — sandbox data is reset every 6 hours; test data and transient state may be cleared. + - Use the [demo (MariaDB)](https://demo.mifos.community) + - Use the [demo (Keycloak)](https://oauth.mifos.community) + - Use the [demo (2FA)](https://2fa.mifos.community) + - Use the [demo (Oidc)](https://oidc.mifos.community) + - Use the [demo (Postgres)](https://elephant.mifos.community) + - Configure to your server by updating API URLs in environment files - **Option B: Install local Fineract server** @@ -132,6 +132,101 @@ When using the development server with basic authentication: - **Build for production:** `ng build --configuration production` or `npm run build:prod` - **Get Angular CLI help:** `ng help` +## Proxy Configuration + +The web app includes a proxy configuration (`proxy.conf.js`) that allows you to forward API requests to a remote Fineract backend during local development. This helps avoid CORS issues and enables you to work against production-like environments. + +### Using the Sandbox Proxy (Default) + +By default, the proxy forwards `/fineract-provider` requests to the Mifos sandbox environment: + +- **Target:** `https://sandbox.mifos.community` +- **API Endpoint:** `https://apis.mifos.community` (exposed in the sandbox) +- **System Reset:** Sandbox test data and transient state are reset every 6 hours (expect data to be periodically cleared). + +**Sandbox Environment Variables:** + +```bash +FINERACT_API_URLS=https://apis.mifos.community +FINERACT_API_URL=https://apis.mifos.community +FINERACT_API_PROVIDER=/fineract-provider/api +FINERACT_API_ACTUATOR=/fineract-provider +FINERACT_API_VERSION=/v1 +FINERACT_PLATFORM_TENANT_IDENTIFIER=default +MIFOS_DEFAULT_LANGUAGE=en-US +MIFOS_SUPPORTED_LANGUAGES=cs-CS,de-DE,en-US,es-MX,fr-FR,it-IT,ko-KO,lt-LT,lv-LV,ne-NE,pt-PT,sw-SW +MIFOS_PRELOAD_CLIENTS=true +MIFOS_DEFAULT_CHAR_DELIMITER=, +``` + +### Using a Local Fineract Instance + +To proxy to a local Fineract server instead: + +Use the provided localhost proxy file (recommended for `ng serve`): + +1. Start the dev server with the localhost proxy: + + ```bash + ng serve --proxy-config proxy.localhost.conf.js + ``` + +2. Ensure your local Fineract instance is running on `http://localhost:8443`. + +Notes: + +- `proxy.localhost.conf.js` forwards `/fineract-provider` to your local backend to avoid CORS during development. +- The `HttpsProxyAgent` / `setupForProxy` logic (present in `proxy.conf.js`) is only necessary when an upstream corporate/HTTP proxy must be used (set via `HTTP_PROXY`/`http_proxy`). It is not required for a direct `localhost` backend. + +### Proxy Features + +- **CORS Avoidance:** Eliminates cross-origin issues during local development +- **Error Handling:** Gracefully handles proxy failures with detailed logging +- **Corporate Proxy Support:** Maintains support for corporate proxy agents via `HTTP_PROXY` environment variable +- **Debug Logging:** All proxy requests are logged for troubleshooting + +The proxy is configured to work with Fineract endpoints as described in this section. + +### Testing the Proxy + +To verify the proxy is working correctly, start the development server (`ng serve`) and test with curl: + +**Successful proxy request:** + +```bash +curl -i "http://localhost:4200/fineract-provider/api/v1/runreports/FullClientReport?R_officeId=1&output-type=HTML&R_loanOfficerId=-1" +``` + +Expected: HTTP 200 response with proxied data from the sandbox. Server console shows: + +```text +[Proxy] Proxying: GET /fineract-provider/api/v1/runreports/... -> https://sandbox.mifos.community/api/v1/runreports/... +``` + +**Simulated proxy error (backend unreachable):** + +If the backend is unreachable or returns an error, the proxy returns HTTP 502: + +```bash +# Stop your Fineract backend (if using localhost) or test with an invalid target +# The proxy will log the error and return: +``` + +Server console (example): + +```text +[Proxy] Error while proxying request: GET /fineract-provider/... -> https://sandbox.mifos.community - ECONNREFUSED +``` + +HTTP response: + +```http +HTTP/1.1 502 Bad Gateway +Content-Type: text/plain + +Proxy error: connect ECONNREFUSED +``` + ## Configuration Options ### Environment Variables for Docker @@ -166,7 +261,7 @@ Available languages: | French | fr | fr-FR.json | | Italian | it | it-IT.json | | Korean | ko | ko-KO.json | -| Lithuanian | li | li-LI.json | +| Lithuanian | lt | lt-LT.json | | Latvian | lv | lv-LV.json | | Nepali | ne | ne-NE.json | | Portuguese | pt | pt-PT.json | diff --git a/docs/backend-proxy.md b/docs/backend-proxy.md index e79b6e573..46e8ffa60 100644 --- a/docs/backend-proxy.md +++ b/docs/backend-proxy.md @@ -18,26 +18,72 @@ The interesting part is there: ```js const proxyConfig = [ { - context: '/api', - pathRewrite: { '^/api': '' }, - target: 'https://api.chucknorris.io', - changeOrigin: true + context: ['/fineract-provider'], + target: 'https://sandbox.mifos.community', + pathRewrite: { '^/fineract-provider': '' }, + changeOrigin: true, + secure: true, + logLevel: 'debug', + onProxyReq: function (proxyReq, req, res) { + // Log the rewritten path so the console shows the actual forwarded URL + const rewrittenPath = (req.url || '').replace(/^\/fineract-provider/, ''); + console.log('[Proxy] Proxying:', req.method, req.url, '->', this.target + rewrittenPath); + }, + onError: function (err, req, res) { + // Log proxy errors and return HTTP 502 to the client + console.error('[Proxy] Error while proxying request:', req.method, req.url, '->', this.target, '-', err.message); + if (res && !res.headersSent) { + res.writeHead(502, { 'Content-Type': 'text/plain' }); + res.end('Proxy error: ' + err.message); + } + } } ]; ``` -This is where you can setup one or more proxy rules. +This forwards requests sent to `/fineract-provider/...` on the dev server to the Fineract backend root (for example, +`/fineract-provider/1.0/...` becomes `https://sandbox.mifos.community/1.0/...`). Use `pathRewrite` to strip the prefix +because the sandbox exposes Fineract APIs at the root path. + +**Error Handling:** + +The `onError` handler captures proxy failures (network errors, backend unreachable, timeouts, etc.) and: + +- Logs detailed error information to the server console (method, URL, target, error message) +- Returns HTTP 502 (Bad Gateway) to the client with a plain-text error message + +Example server console output when proxy fails: + +```text +[Proxy] Error while proxying request: GET /fineract-provider/api/v1/clients -> https://sandbox.mifos.community - ECONNREFUSED +``` + +Example client response: + +```http +HTTP/1.1 502 Bad Gateway +Content-Type: text/plain + +Proxy error: connect ECONNREFUSED +``` + +You can add multiple rules or change the `target` to point to other environments (demo, localhost, etc.). For the complete set of options, see the `http-proxy-middleware` [documentation](https://github.com/chimurai/http-proxy-middleware#options). ### Corporate proxy support -To allow external API calls redirection through a corporate proxy, you will also find a `setupForCorporateProxy()` -function in the proxy configuration file. By default, this method configures a corporate proxy agent based on the -`HTTP_PROXY` environment variable, see the [corporate proxy documentation](corporate-proxy.md) for more details. +The repository configures a helper function in `proxy.conf.js` named `setupForProxy` which will attach an +`HttpsProxyAgent` to proxy entries when the `HTTP_PROXY` (or `http_proxy`) environment variable is present. This lets +the dev server forward requests through a corporate proxy when required. -If you need to, you can further customize this function to fit the network of your working environment. +**Behavior:** -If your corporate proxy use a custom SSL certificate, your may need to add the `secure: false` option to your -backend proxy configuration. +- **When `HTTP_PROXY` is set:** Logs `"Using proxy server: "` and configures the agent for corporate proxy forwarding +- **When `HTTP_PROXY` is not set:** Logs `"No proxy server configured. API requests will not be proxied."` — note that this refers only to corporate proxy forwarding; the Angular dev server proxy (forwarding `/fineract-provider` to the sandbox) still works normally + +See `proxy.conf.js` for the exact implementation. + +If your corporate proxy uses a custom SSL certificate you may need to set `secure: false` on the specific proxy entry +or configure your environment to trust the corporate CA. diff --git a/proxy.conf.js b/proxy.conf.js index 839646ab6..c6fed5ebb 100644 --- a/proxy.conf.js +++ b/proxy.conf.js @@ -2,7 +2,7 @@ const { HttpsProxyAgent } = require('https-proxy-agent'); -/* +/* IMPORTANT: * API proxy configuration. * This allows you to proxy HTTP request like `http.get('/api/stuff')` to another server/port. * This is especially useful during app development to avoid CORS issues while running a local server. @@ -10,12 +10,33 @@ const { HttpsProxyAgent } = require('https-proxy-agent'); */ const proxyConfig = [ { - context: '/api', - pathRewrite: { '^/api': '' }, - target: 'https://api.chucknorris.io', + context: ['/fineract-provider'], + target: 'https://sandbox.mifos.community', + pathRewrite: { '^/fineract-provider': '' }, changeOrigin: true, - secure: false + secure: true, + logLevel: 'debug', + onProxyReq: function (proxyReq, req, res) { + const rewrittenPath = (req.url || '').replace(/^\/fineract-provider/, ''); + console.log('[Proxy] Proxying:', req.method, req.url, '->', this.target + rewrittenPath); + }, + onError: function (err, req, res) { + console.error( + '[Proxy] Error while proxying request:', + req && req.method, + req && req.url, + '->', + this.target, + '-', + err && err.message + ); + if (res && !res.headersSent) { + res.writeHead(502, { 'Content-Type': 'text/plain' }); + res.end('Proxy error: ' + (err && err.message ? err.message : 'Unknown error')); + } + } } + // For local development use `proxy.localhost.conf js` . ]; /* diff --git a/proxy.localhost.conf.js b/proxy.localhost.conf.js new file mode 100644 index 000000000..58193b7a7 --- /dev/null +++ b/proxy.localhost.conf.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Proxy configuration for running the app against a local Fineract instance. + * Usage: + * ng serve --proxy-config proxy.localhost.conf.js + */ + +module.exports = [ + { + context: ['/fineract-provider'], + target: 'http://localhost:8443', + pathRewrite: { '^/fineract-provider': '' }, + changeOrigin: true, + secure: false, + logLevel: 'debug', + onProxyReq: function (proxyReq, req, res) { + const rewrittenPath = (req.url || '').replace(/^\/fineract-provider/, ''); + console.log('[Proxy] Proxying:', req.method, req.url, '->', this.target + rewrittenPath); + }, + onError: function (err, req, res) { + console.error( + '[Proxy] Error while proxying request:', + req && req.method, + req && req.url, + '->', + this.target, + '-', + err && err.message + ); + if (res && !res.headersSent) { + res.writeHead(502, { 'Content-Type': 'text/plain' }); + res.end('Proxy error: ' + (err && err.message ? err.message : 'Unknown error')); + } + } + } +];