@flowerforce/flowerbase 1.8.1-beta.1 → 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 +7 -0
- package/README.md +41 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +2 -0
- package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
- package/dist/utils/initializer/exposeRoutes.js +40 -6
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/features/functions/__tests__/controller.test.ts +60 -0
- package/src/features/functions/controller.ts +2 -0
- package/src/utils/__tests__/exposeRoutes.test.ts +26 -1
- package/src/utils/initializer/exposeRoutes.ts +39 -5
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -484,6 +484,7 @@ Ensure the following environment variables are set in your .env file or deployme
|
|
|
484
484
|
| `PORT` | The port on which the server will run. | `3000` |
|
|
485
485
|
| `MONGODB_URL` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
|
|
486
486
|
| `JWT_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
|
|
487
|
+
| `FUNCTION_CALL_BODY_LIMIT_BYTES` | Max request body size in bytes for `POST /api/client/<version>/app/:appId/functions/call`. | `52428800` |
|
|
487
488
|
| `HOST` | The host address the server binds to (usually `0.0.0.0` for public access). | `0.0.0.0` |
|
|
488
489
|
| `API_VERSION` | API version used in client base path. | `v2.0` |
|
|
489
490
|
| `HTTPS_SCHEMA` | The schema for your server requests (usually `https` or `http`). | `http` |
|
|
@@ -518,6 +519,7 @@ PROJECT_ID=your-project-id
|
|
|
518
519
|
PORT=3000
|
|
519
520
|
MONGODB_URL=mongodb+srv://username:password@cluster.mongodb.net/dbname
|
|
520
521
|
JWT_SECRET=your-jwt-secret
|
|
522
|
+
FUNCTION_CALL_BODY_LIMIT_BYTES=52428800
|
|
521
523
|
HOST=0.0.0.0
|
|
522
524
|
API_VERSION=v2.0
|
|
523
525
|
HTTPS_SCHEMA=http
|
|
@@ -555,6 +557,45 @@ MONIT_ALLOW_EDIT=true
|
|
|
555
557
|
- If `MONIT_ALLOWED_IPS` is set, only those IPs can reach `/monit` (ensure `req.ip` reflects your proxy setup / `trustProxy`).
|
|
556
558
|
- You can disable **invoke** or **edit** with `MONIT_ALLOW_INVOKE=false` and/or `MONIT_ALLOW_EDIT=false`.
|
|
557
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
|
+
|
|
558
599
|
|
|
559
600
|
<a id="build"></a>
|
|
560
601
|
## 🚀 Build & Deploy the Server
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAsBpC,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAsBpC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCsB,eAAe,EAAE;;;;;;CAMjE,CAAA;AACD,eAAO,MAAM,WAAW,QAA8C,CAAA;AACtE,eAAO,MAAM,YAAY,QAA8B,CAAA;AACvD,eAAO,MAAM,OAAO,QAAgB,CAAA;AACpC,eAAO,MAAM,YAAY,QAAiC,CAAA;AAE1D,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7E,eAAO,MAAM,WAAW;;;;;;;mBAOqB,aAAa;;;;;;;;;CAOzD,CAAA;AAED,eAAO,MAAM,SAAS;;;CAGrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAA"}
|
package/dist/constants.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.DEFAULT_CONFIG = {
|
|
|
31
31
|
PORT: Number(process.env.PORT) || 3000,
|
|
32
32
|
MONGODB_URL: process.env.MONGODB_URL || '',
|
|
33
33
|
JWT_SECRET: process.env.JWT_SECRET || '',
|
|
34
|
+
FUNCTION_CALL_BODY_LIMIT_BYTES: Number(process.env.FUNCTION_CALL_BODY_LIMIT_BYTES) || 50 * 1024 * 1024,
|
|
34
35
|
API_VERSION: process.env.API_VERSION || 'v2.0',
|
|
35
36
|
HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
|
|
36
37
|
HOST: process.env.HOST || '0.0.0.0',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAKvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAmHhD,eAAO,MAAM,iCAAiC,GAAI,OAAO,OAAO,KAAG,OA0BlE,CAAA;AAID,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,OAgD9D,CAAA;AAqHD,eAAO,MAAM,oCAAoC,GAAI,QAAQ,QAAQ,YAClC,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,kBA0RjC,CAAA"}
|
|
@@ -22,6 +22,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.functionsController = exports.shouldSkipReadabilityLookupForChange = exports.mapWatchFilterToDocumentQuery = exports.mapWatchFilterToChangeStreamMatch = void 0;
|
|
24
24
|
const bson_1 = require("bson");
|
|
25
|
+
const constants_1 = require("../../constants");
|
|
25
26
|
const services_1 = require("../../services");
|
|
26
27
|
const context_1 = require("../../utils/context");
|
|
27
28
|
const utils_1 = require("./utils");
|
|
@@ -273,6 +274,7 @@ exports.shouldSkipReadabilityLookupForChange = shouldSkipReadabilityLookupForCha
|
|
|
273
274
|
const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0, function* (app, { functionsList, rules }) {
|
|
274
275
|
app.addHook('preHandler', app.jwtAuthentication);
|
|
275
276
|
app.post('/call', {
|
|
277
|
+
bodyLimit: constants_1.DEFAULT_CONFIG.FUNCTION_CALL_BODY_LIMIT_BYTES,
|
|
276
278
|
schema: {
|
|
277
279
|
tags: ['Functions']
|
|
278
280
|
}
|
|
@@ -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;
|
|
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
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
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
package/src/constants.ts
CHANGED
|
@@ -25,6 +25,7 @@ export const DEFAULT_CONFIG = {
|
|
|
25
25
|
PORT: Number(process.env.PORT) || 3000,
|
|
26
26
|
MONGODB_URL: process.env.MONGODB_URL || '',
|
|
27
27
|
JWT_SECRET: process.env.JWT_SECRET || '',
|
|
28
|
+
FUNCTION_CALL_BODY_LIMIT_BYTES: Number(process.env.FUNCTION_CALL_BODY_LIMIT_BYTES) || 50 * 1024 * 1024,
|
|
28
29
|
API_VERSION: process.env.API_VERSION || 'v2.0',
|
|
29
30
|
HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
|
|
30
31
|
HOST: process.env.HOST || '0.0.0.0',
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
import { GenerateContext } from '../../../utils/context'
|
|
3
|
+
import { functionsController } from '../controller'
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../utils/context', () => ({
|
|
6
|
+
GenerateContext: jest.fn()
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
describe('functionsController', () => {
|
|
10
|
+
let app: FastifyInstance
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
app = Fastify()
|
|
14
|
+
|
|
15
|
+
app.decorate('jwtAuthentication', async (request: FastifyRequest, _reply: FastifyReply) => {
|
|
16
|
+
;(request as any).user = {
|
|
17
|
+
id: '507f191e810c19729de860ea',
|
|
18
|
+
typ: 'access'
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
;(GenerateContext as jest.Mock).mockResolvedValue({ ok: true })
|
|
23
|
+
|
|
24
|
+
await app.register(functionsController, {
|
|
25
|
+
functionsList: {
|
|
26
|
+
largePayloadEcho: {
|
|
27
|
+
code: 'exports = () => ({ ok: true })'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
rules: {}
|
|
31
|
+
})
|
|
32
|
+
await app.ready()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await app.close()
|
|
37
|
+
jest.clearAllMocks()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('accepts payloads larger than Fastify default body limit on POST /call', async () => {
|
|
41
|
+
const largeValue = 'x'.repeat(2 * 1024 * 1024)
|
|
42
|
+
|
|
43
|
+
const response = await app.inject({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
url: '/call',
|
|
46
|
+
payload: {
|
|
47
|
+
name: 'largePayloadEcho',
|
|
48
|
+
arguments: [{ largeValue }]
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
expect(response.statusCode).toBe(200)
|
|
53
|
+
expect(JSON.parse(response.body)).toEqual({ ok: true })
|
|
54
|
+
expect(GenerateContext).toHaveBeenCalledWith(
|
|
55
|
+
expect.objectContaining({
|
|
56
|
+
args: [{ largeValue }]
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -2,6 +2,7 @@ import type { ServerResponse } from 'http'
|
|
|
2
2
|
import { EJSON, ObjectId } from 'bson'
|
|
3
3
|
import type { FastifyRequest } from 'fastify'
|
|
4
4
|
import type { Document } from 'mongodb'
|
|
5
|
+
import { DEFAULT_CONFIG } from '../../constants'
|
|
5
6
|
import { services } from '../../services'
|
|
6
7
|
import { GenerateContext } from '../../utils/context'
|
|
7
8
|
import { Base64Function, FunctionCallBase64Dto, FunctionCallDto } from './dtos'
|
|
@@ -331,6 +332,7 @@ export const functionsController: FunctionController = async (
|
|
|
331
332
|
app.addHook('preHandler', app.jwtAuthentication)
|
|
332
333
|
|
|
333
334
|
app.post<{ Body: FunctionCallDto }>('/call', {
|
|
335
|
+
bodyLimit: DEFAULT_CONFIG.FUNCTION_CALL_BODY_LIMIT_BYTES,
|
|
334
336
|
schema: {
|
|
335
337
|
tags: ['Functions']
|
|
336
338
|
}
|
|
@@ -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
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
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 {
|