@flowerforce/flowerbase 1.8.1-beta.2 → 1.8.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 1.8.1 (2026-03-27)
2
+
3
+
4
+ ### 🩹 Fixes
5
+
6
+ - add config FUNCTION_CALL_BODY_LIMIT ([5f6ec70](https://github.com/flowerforce/flowerbase/commit/5f6ec70))
7
+
1
8
  ## 1.8.0 (2026-03-20)
2
9
 
3
10
 
package/README.md CHANGED
@@ -557,6 +557,45 @@ MONIT_ALLOW_EDIT=true
557
557
  - If `MONIT_ALLOWED_IPS` is set, only those IPs can reach `/monit` (ensure `req.ip` reflects your proxy setup / `trustProxy`).
558
558
  - You can disable **invoke** or **edit** with `MONIT_ALLOW_INVOKE=false` and/or `MONIT_ALLOW_EDIT=false`.
559
559
 
560
+ ### 🔐 Reverse Proxy and SSL Termination
561
+
562
+ When Flowerbase runs behind Nginx or HAProxy with SSL termination, make sure the proxy forwards the public scheme/host/port headers.
563
+
564
+ Flowerbase uses these headers for `GET /api/client/<version>/app/:appId/location`, and Realm-style clients use that response to build auth URLs (including `/login`).
565
+
566
+ - Header priority for host and scheme is: `Forwarded` first, then `X-Forwarded-*`, then `Host`.
567
+ - If `X-Forwarded-Port` is set and host has no explicit port, Flowerbase appends it.
568
+
569
+ #### Nginx
570
+
571
+ ```nginx
572
+ location / {
573
+ proxy_pass http://flowerbase_upstream;
574
+ proxy_set_header Host $http_host;
575
+ proxy_set_header X-Forwarded-Proto $scheme;
576
+ proxy_set_header X-Forwarded-Host $http_host;
577
+ proxy_set_header X-Forwarded-Port $server_port;
578
+ }
579
+ ```
580
+
581
+ #### HAProxy
582
+
583
+ ```haproxy
584
+ frontend fe_https
585
+ bind *:443 ssl crt /etc/haproxy/certs/site.pem
586
+ mode http
587
+ default_backend be_flowerbase
588
+
589
+ backend be_flowerbase
590
+ mode http
591
+ server app1 127.0.0.1:3000 check
592
+ http-request set-header Host %[req.hdr(host)]
593
+ http-request set-header X-Forwarded-Proto https if { ssl_fc }
594
+ http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
595
+ http-request set-header X-Forwarded-Host %[req.hdr(host)]
596
+ http-request set-header X-Forwarded-Port %[dst_port]
597
+ ```
598
+
560
599
 
561
600
  <a id="build"></a>
562
601
  ## 🚀 Build & Deploy the Server
@@ -1 +1 @@
1
- {"version":3,"file":"exposeRoutes.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/exposeRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,eAAe,kBAqF1D,CAAA"}
1
+ {"version":3,"file":"exposeRoutes.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/exposeRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAmCzC;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,eAAe,kBA2F1D,CAAA"}
@@ -15,6 +15,36 @@ const utils_1 = require("../../auth/utils");
15
15
  const constants_1 = require("../../constants");
16
16
  const handleUserRegistration_model_1 = require("../../shared/models/handleUserRegistration.model");
17
17
  const crypto_1 = require("../crypto");
18
+ const parseFirstHeaderValue = (header) => {
19
+ var _a;
20
+ if (!header)
21
+ return undefined;
22
+ const raw = Array.isArray(header) ? header[0] : header;
23
+ return ((_a = raw === null || raw === void 0 ? void 0 : raw.split(',')[0]) === null || _a === void 0 ? void 0 : _a.trim()) || undefined;
24
+ };
25
+ const parseForwardedHeader = (header) => {
26
+ var _a;
27
+ if (!header)
28
+ return {};
29
+ const segment = (_a = header.split(',')[0]) === null || _a === void 0 ? void 0 : _a.trim();
30
+ if (!segment)
31
+ return {};
32
+ const tokens = segment.split(';').map((item) => item.trim());
33
+ const protoToken = tokens.find((item) => item.toLowerCase().startsWith('proto='));
34
+ const hostToken = tokens.find((item) => item.toLowerCase().startsWith('host='));
35
+ const clean = (value) => value === null || value === void 0 ? void 0 : value.replace(/^"|"$/g, '');
36
+ return {
37
+ proto: clean(protoToken === null || protoToken === void 0 ? void 0 : protoToken.split('=')[1]),
38
+ host: clean(hostToken === null || hostToken === void 0 ? void 0 : hostToken.split('=')[1])
39
+ };
40
+ };
41
+ const hasExplicitPort = (host) => {
42
+ if (/^\[[^\]]+]\:\d+$/.test(host))
43
+ return true;
44
+ if (/^[^:]+\:\d+$/.test(host))
45
+ return true;
46
+ return false;
47
+ };
18
48
  /**
19
49
  * > Used to expose all app routes
20
50
  * @param fastify -> the fastify instance
@@ -27,12 +57,16 @@ const exposeRoutes = (fastify) => __awaiter(void 0, void 0, void 0, function* ()
27
57
  tags: ['System']
28
58
  }
29
59
  }, (req) => __awaiter(void 0, void 0, void 0, function* () {
30
- var _a, _b, _c;
31
- const schema = (_a = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.HTTPS_SCHEMA) !== null && _a !== void 0 ? _a : 'http';
32
- const headerHost = (_b = req.headers.host) !== null && _b !== void 0 ? _b : 'localhost:3000';
33
- const hostname = headerHost.split(':')[0];
34
- const port = (_c = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.PORT) !== null && _c !== void 0 ? _c : 3000;
35
- const host = process.env.NODE_ENV === "production" ? hostname : `${hostname}:${port}`;
60
+ var _a, _b, _c, _d, _e, _f, _g;
61
+ const forwarded = parseForwardedHeader(parseFirstHeaderValue(req.headers.forwarded));
62
+ const forwardedProto = parseFirstHeaderValue(req.headers['x-forwarded-proto']);
63
+ const forwardedHost = parseFirstHeaderValue(req.headers['x-forwarded-host']);
64
+ const forwardedPort = parseFirstHeaderValue(req.headers['x-forwarded-port']);
65
+ const schema = (_c = (_b = (_a = forwarded.proto) !== null && _a !== void 0 ? _a : forwardedProto) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.HTTPS_SCHEMA) !== null && _c !== void 0 ? _c : 'http';
66
+ let host = (_f = (_e = (_d = forwarded.host) !== null && _d !== void 0 ? _d : forwardedHost) !== null && _e !== void 0 ? _e : req.headers.host) !== null && _f !== void 0 ? _f : `localhost:${(_g = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.PORT) !== null && _g !== void 0 ? _g : 3000}`;
67
+ if (forwardedPort && !hasExplicitPort(host)) {
68
+ host = `${host}:${forwardedPort}`;
69
+ }
36
70
  const wsSchema = 'wss';
37
71
  return {
38
72
  deployment_model: 'LOCAL',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.8.1-beta.2",
3
+ "version": "1.8.1-beta.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,10 @@ describe('exposeRoutes', () => {
38
38
  const appId = 'it'
39
39
  const response = await config.app!.inject({
40
40
  method: 'GET',
41
- url: `${API_VERSION}/app/${appId}/location`
41
+ url: `${API_VERSION}/app/${appId}/location`,
42
+ headers: {
43
+ host: 'localhost:3000'
44
+ }
42
45
  })
43
46
 
44
47
  expect(response.statusCode).toBe(200)
@@ -50,6 +53,28 @@ describe('exposeRoutes', () => {
50
53
  })
51
54
  })
52
55
 
56
+ it('GET location should prioritize forwarded host and port', async () => {
57
+ const appId = 'it'
58
+ const response = await config.app!.inject({
59
+ method: 'GET',
60
+ url: `${API_VERSION}/app/${appId}/location`,
61
+ headers: {
62
+ host: 'internal-flowerbase:3000',
63
+ 'x-forwarded-proto': 'https',
64
+ 'x-forwarded-host': 'api.example.com',
65
+ 'x-forwarded-port': '8443'
66
+ }
67
+ })
68
+
69
+ expect(response.statusCode).toBe(200)
70
+ expect(JSON.parse(response.body)).toEqual({
71
+ deployment_model: 'LOCAL',
72
+ location: 'IE',
73
+ hostname: 'https://api.example.com:8443',
74
+ ws_hostname: 'wss://api.example.com:8443'
75
+ })
76
+ })
77
+
53
78
  it('exposeRoutes should handle errors in the catch block', async () => {
54
79
  const mockedApp = Fastify()
55
80
  // Forced fail on get method
@@ -6,6 +6,34 @@ import { API_VERSION, AUTH_CONFIG, AUTH_DB_NAME, DEFAULT_CONFIG } from '../../co
6
6
  import { PROVIDER } from '../../shared/models/handleUserRegistration.model'
7
7
  import { hashPassword } from '../crypto'
8
8
 
9
+ const parseFirstHeaderValue = (header: string | string[] | undefined): string | undefined => {
10
+ if (!header) return undefined
11
+ const raw = Array.isArray(header) ? header[0] : header
12
+ return raw?.split(',')[0]?.trim() || undefined
13
+ }
14
+
15
+ const parseForwardedHeader = (header: string | undefined): { proto?: string; host?: string } => {
16
+ if (!header) return {}
17
+ const segment = header.split(',')[0]?.trim()
18
+ if (!segment) return {}
19
+
20
+ const tokens = segment.split(';').map((item) => item.trim())
21
+ const protoToken = tokens.find((item) => item.toLowerCase().startsWith('proto='))
22
+ const hostToken = tokens.find((item) => item.toLowerCase().startsWith('host='))
23
+ const clean = (value?: string) => value?.replace(/^"|"$/g, '')
24
+
25
+ return {
26
+ proto: clean(protoToken?.split('=')[1]),
27
+ host: clean(hostToken?.split('=')[1])
28
+ }
29
+ }
30
+
31
+ const hasExplicitPort = (host: string): boolean => {
32
+ if (/^\[[^\]]+]\:\d+$/.test(host)) return true
33
+ if (/^[^:]+\:\d+$/.test(host)) return true
34
+ return false
35
+ }
36
+
9
37
  /**
10
38
  * > Used to expose all app routes
11
39
  * @param fastify -> the fastify instance
@@ -18,11 +46,17 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
18
46
  tags: ['System']
19
47
  }
20
48
  }, async (req) => {
21
- const schema = DEFAULT_CONFIG?.HTTPS_SCHEMA ?? 'http'
22
- const headerHost = req.headers.host ?? 'localhost:3000'
23
- const hostname = headerHost.split(':')[0]
24
- const port = DEFAULT_CONFIG?.PORT ?? 3000
25
- const host = process.env.NODE_ENV === "production" ? hostname : `${hostname}:${port}`
49
+ const forwarded = parseForwardedHeader(parseFirstHeaderValue(req.headers.forwarded))
50
+ const forwardedProto = parseFirstHeaderValue(req.headers['x-forwarded-proto'])
51
+ const forwardedHost = parseFirstHeaderValue(req.headers['x-forwarded-host'])
52
+ const forwardedPort = parseFirstHeaderValue(req.headers['x-forwarded-port'])
53
+ const schema = forwarded.proto ?? forwardedProto ?? DEFAULT_CONFIG?.HTTPS_SCHEMA ?? 'http'
54
+ let host = forwarded.host ?? forwardedHost ?? req.headers.host ?? `localhost:${DEFAULT_CONFIG?.PORT ?? 3000}`
55
+
56
+ if (forwardedPort && !hasExplicitPort(host)) {
57
+ host = `${host}:${forwardedPort}`
58
+ }
59
+
26
60
  const wsSchema = 'wss'
27
61
 
28
62
  return {