underpost 2.8.883 → 2.8.884
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/README.md +6 -2
- package/cli.md +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/src/api/user/user.service.js +3 -10
- package/src/client/components/core/Modal.js +9 -10
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +262 -0
- package/src/runtime/lampp/Lampp.js +27 -8
- package/src/server/auth.js +74 -16
- package/src/server/runtime.js +54 -208
- package/src/runtime/nginx/Nginx.js +0 -3
package/README.md
CHANGED
|
@@ -68,13 +68,15 @@
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
|
|
72
|
+
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
|
|
74
76
|
<!-- badges -->
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
[](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [](https://www.npmjs.com/package/underpost) [](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [](https://www.npmjs.com/package/underpost) [](https://socket.dev/npm/package/underpost/overview/2.8.884) [](https://coveralls.io/github/underpostnet/engine?branch=master) [](https://www.npmjs.org/package/underpost) [](https://www.npmjs.com/package/underpost)
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
<!-- end-badges -->
|
|
@@ -131,6 +133,8 @@
|
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
|
|
136
|
+
|
|
137
|
+
|
|
134
138
|
|
|
135
139
|
|
|
136
140
|
|
|
@@ -178,7 +182,7 @@ Run dev client server
|
|
|
178
182
|
npm run dev
|
|
179
183
|
```
|
|
180
184
|
<!-- -->
|
|
181
|
-
## underpost ci/cd cli v2.8.
|
|
185
|
+
## underpost ci/cd cli v2.8.884
|
|
182
186
|
|
|
183
187
|
### Usage: `underpost [options] [command]`
|
|
184
188
|
```
|
package/cli.md
CHANGED
|
@@ -17,7 +17,7 @@ spec:
|
|
|
17
17
|
spec:
|
|
18
18
|
containers:
|
|
19
19
|
- name: dd-default-development-blue
|
|
20
|
-
image: localhost/rockylinux9-underpost:v2.8.
|
|
20
|
+
image: localhost/rockylinux9-underpost:v2.8.884
|
|
21
21
|
# resources:
|
|
22
22
|
# requests:
|
|
23
23
|
# memory: "124Ki"
|
|
@@ -100,7 +100,7 @@ spec:
|
|
|
100
100
|
spec:
|
|
101
101
|
containers:
|
|
102
102
|
- name: dd-default-development-green
|
|
103
|
-
image: localhost/rockylinux9-underpost:v2.8.
|
|
103
|
+
image: localhost/rockylinux9-underpost:v2.8.884
|
|
104
104
|
# resources:
|
|
105
105
|
# requests:
|
|
106
106
|
# memory: "124Ki"
|
|
@@ -17,7 +17,7 @@ spec:
|
|
|
17
17
|
spec:
|
|
18
18
|
containers:
|
|
19
19
|
- name: dd-test-development-blue
|
|
20
|
-
image: localhost/rockylinux9-underpost:v2.8.
|
|
20
|
+
image: localhost/rockylinux9-underpost:v2.8.884
|
|
21
21
|
# resources:
|
|
22
22
|
# requests:
|
|
23
23
|
# memory: "96294Ki"
|
|
@@ -104,7 +104,7 @@ spec:
|
|
|
104
104
|
spec:
|
|
105
105
|
containers:
|
|
106
106
|
- name: dd-test-development-green
|
|
107
|
-
image: localhost/rockylinux9-underpost:v2.8.
|
|
107
|
+
image: localhost/rockylinux9-underpost:v2.8.884
|
|
108
108
|
# resources:
|
|
109
109
|
# requests:
|
|
110
110
|
# memory: "96294Ki"
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
createSessionAndUserToken,
|
|
7
7
|
createUserAndSession,
|
|
8
8
|
refreshSessionAndToken,
|
|
9
|
-
|
|
9
|
+
logoutSession,
|
|
10
10
|
jwtSign,
|
|
11
11
|
getBearerToken,
|
|
12
12
|
validatePasswordMiddleware,
|
|
@@ -382,15 +382,8 @@ const UserService = {
|
|
|
382
382
|
const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
|
|
383
383
|
|
|
384
384
|
if (req.params.id === 'logout') {
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
const hashedToken = hashToken(refreshToken);
|
|
388
|
-
await User.updateOne(
|
|
389
|
-
{ 'activeSessions.tokenHash': hashedToken },
|
|
390
|
-
{ $pull: { activeSessions: { tokenHash: hashedToken } } },
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
res.clearCookie('refreshToken');
|
|
385
|
+
const result = await logoutSession(User, req, res);
|
|
386
|
+
if (!result) throw new Error('Logout failed');
|
|
394
387
|
return { message: 'Logged out successfully' };
|
|
395
388
|
}
|
|
396
389
|
|
|
@@ -2191,14 +2191,13 @@ const Modal = {
|
|
|
2191
2191
|
: { ...Modal.subMenuBtnClass, _: { btnSelector, labelSelector } };
|
|
2192
2192
|
|
|
2193
2193
|
for (const keyDataBtn of Object.keys(_data)) {
|
|
2194
|
-
const {
|
|
2194
|
+
const { labelSelector, top } = _data[keyDataBtn];
|
|
2195
2195
|
if (top)
|
|
2196
2196
|
setTimeout(() => {
|
|
2197
2197
|
top();
|
|
2198
2198
|
});
|
|
2199
|
-
if (open) continue;
|
|
2200
2199
|
sa(labelSelector).forEach((el) => {
|
|
2201
|
-
el.classList.add('hide');
|
|
2200
|
+
if (!el.classList.contains('hide')) el.classList.add('hide');
|
|
2202
2201
|
el.style.transition = null;
|
|
2203
2202
|
});
|
|
2204
2203
|
|
|
@@ -2425,9 +2424,6 @@ const subMenuRender = async (subMenuId) => {
|
|
|
2425
2424
|
|
|
2426
2425
|
if (!menuBtn || !menuContainer || !arrow) return;
|
|
2427
2426
|
|
|
2428
|
-
// if (Modal.subMenuBtnClass[subMenuId] && !(isSubMenuOpen(subMenuId) && Modal.subMenuBtnClass[subMenuId].open === true))
|
|
2429
|
-
// Modal.subMenuBtnClass[subMenuId].open = false;
|
|
2430
|
-
|
|
2431
2427
|
const top = () => {
|
|
2432
2428
|
menuContainer.style.top = menuBtn.offsetTop + Modal.Data['modal-menu'].options.heightTopBar + 'px';
|
|
2433
2429
|
};
|
|
@@ -2442,8 +2438,7 @@ const subMenuRender = async (subMenuId) => {
|
|
|
2442
2438
|
menuBtn.style.transition = '.3s';
|
|
2443
2439
|
arrow.style.transition = '.3s';
|
|
2444
2440
|
|
|
2445
|
-
if (
|
|
2446
|
-
Modal.subMenuBtnClass[subMenuId].open = false;
|
|
2441
|
+
if (isSubMenuOpen(subMenuId)) {
|
|
2447
2442
|
// Close animation
|
|
2448
2443
|
menuContainer.style.overflow = 'hidden';
|
|
2449
2444
|
menuContainer.style.height = '0px';
|
|
@@ -2453,8 +2448,12 @@ const subMenuRender = async (subMenuId) => {
|
|
|
2453
2448
|
arrow.style.rotate = '0deg';
|
|
2454
2449
|
});
|
|
2455
2450
|
} else {
|
|
2456
|
-
|
|
2457
|
-
|
|
2451
|
+
sa(`.menu-label-text-${subMenuId}`).forEach((el) => {
|
|
2452
|
+
if (!el.classList.contains('hide')) el.classList.add('hide');
|
|
2453
|
+
});
|
|
2454
|
+
setTimeout(() => {
|
|
2455
|
+
Modal.menuTextLabelAnimation('modal-menu', subMenuId);
|
|
2456
|
+
});
|
|
2458
2457
|
// Open animation
|
|
2459
2458
|
setTimeout(top, 360);
|
|
2460
2459
|
menuContainer.style.width = '320px';
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A service dedicated to creating and configuring an Express.js application
|
|
3
|
+
* instance based on server configuration data.
|
|
4
|
+
* @module src/runtime/express/Express.js
|
|
5
|
+
* @namespace ExpressService
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import express from 'express';
|
|
10
|
+
import fileUpload from 'express-fileupload';
|
|
11
|
+
import swaggerUi from 'swagger-ui-express';
|
|
12
|
+
import compression from 'compression';
|
|
13
|
+
import { createServer } from 'http';
|
|
14
|
+
|
|
15
|
+
import UnderpostStartUp from '../../server/start.js';
|
|
16
|
+
import { loggerFactory, loggerMiddleware } from '../../server/logger.js';
|
|
17
|
+
import { getCapVariableName, newInstance } from '../../client/components/core/CommonJs.js';
|
|
18
|
+
import { MailerProvider } from '../../mailer/MailerProvider.js';
|
|
19
|
+
import { DataBaseProvider } from '../../db/DataBaseProvider.js';
|
|
20
|
+
import { createPeerServer } from '../../server/peer.js';
|
|
21
|
+
import { createValkeyConnection } from '../../server/valkey.js';
|
|
22
|
+
import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
|
|
23
|
+
import { ssrMiddlewareFactory } from '../../server/ssr.js';
|
|
24
|
+
|
|
25
|
+
const logger = loggerFactory(import.meta);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @class ExpressService
|
|
29
|
+
* @description A service dedicated to creating and configuring an Express.js application
|
|
30
|
+
* instance based on server configuration data.
|
|
31
|
+
* @memberof ExpressService
|
|
32
|
+
*/
|
|
33
|
+
class ExpressService {
|
|
34
|
+
/**
|
|
35
|
+
* Creates and configures a complete Express application instance for a specific host/path configuration.
|
|
36
|
+
*
|
|
37
|
+
* @method createApp
|
|
38
|
+
* @memberof ExpressService
|
|
39
|
+
* @param {string} config.host - The host name for the instance (e.g., 'www.example.com').
|
|
40
|
+
* @param {string} config.path - The URL path for the instance (e.g., '/', '/api/v1').
|
|
41
|
+
* @param {number} config.port - The primary listening port for the instance.
|
|
42
|
+
* @param {string} config.client - The client associated with the instance (used for SSR/Mailer configuration lookup).
|
|
43
|
+
* @param {string[]} [config.apis] - A list of API names to load and attach routers for.
|
|
44
|
+
* @param {string[]} config.origins - Allowed origins for CORS.
|
|
45
|
+
* @param {string} [config.directory] - The directory for static files (if overriding default).
|
|
46
|
+
* @param {string} [config.ws] - The WebSocket server name to use.
|
|
47
|
+
* @param {object} [config.mailer] - Mailer configuration.
|
|
48
|
+
* @param {object} [config.db] - Database configuration.
|
|
49
|
+
* @param {string} [config.redirect] - URL or flag to indicate an HTTP redirect should be configured.
|
|
50
|
+
* @param {boolean} [config.peer] - Whether to enable the peer server.
|
|
51
|
+
* @param {object} [config.valkey] - Valkey connection configuration.
|
|
52
|
+
* @param {string} [config.apiBaseHost] - Base host for the API (if running separate API).
|
|
53
|
+
* @param {number} [config.devApiPort] - The dynamically calculated development API port used for CORS in dev mode.
|
|
54
|
+
* @param {string} config.redirectTarget - The full target URL for redirection (used if `redirect` is true).
|
|
55
|
+
* @param {string} config.rootHostPath - The root path for public host assets (e.g., `/public/hostname`).
|
|
56
|
+
* @param {object} config.confSSR - The SSR configuration object, used to look up Mailer templates.
|
|
57
|
+
* @param {import('prom-client').Counter<string>} config.promRequestCounter - Prometheus request counter instance.
|
|
58
|
+
* @param {import('prom-client').Registry} config.promRegister - Prometheus register instance for metrics.
|
|
59
|
+
* @returns {Promise<{portsUsed: number}>} An object indicating how many additional ports were used (e.g., for PeerServer).
|
|
60
|
+
*/
|
|
61
|
+
async createApp({
|
|
62
|
+
host,
|
|
63
|
+
path,
|
|
64
|
+
port,
|
|
65
|
+
client,
|
|
66
|
+
apis,
|
|
67
|
+
origins,
|
|
68
|
+
directory,
|
|
69
|
+
ws,
|
|
70
|
+
mailer,
|
|
71
|
+
db,
|
|
72
|
+
redirect,
|
|
73
|
+
peer,
|
|
74
|
+
valkey,
|
|
75
|
+
apiBaseHost,
|
|
76
|
+
devApiPort, // New parameter for dev environment CORS
|
|
77
|
+
redirectTarget,
|
|
78
|
+
rootHostPath,
|
|
79
|
+
confSSR,
|
|
80
|
+
promRequestCounter,
|
|
81
|
+
promRegister,
|
|
82
|
+
}) {
|
|
83
|
+
let portsUsed = 0;
|
|
84
|
+
const runningData = {
|
|
85
|
+
host,
|
|
86
|
+
path,
|
|
87
|
+
runtime: 'nodejs',
|
|
88
|
+
client,
|
|
89
|
+
meta: import.meta,
|
|
90
|
+
apis,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const app = express();
|
|
94
|
+
|
|
95
|
+
if (process.env.NODE_ENV === 'production') app.set('trust proxy', true);
|
|
96
|
+
|
|
97
|
+
app.use((req, res, next) => {
|
|
98
|
+
res.on('finish', () => {
|
|
99
|
+
promRequestCounter.inc({
|
|
100
|
+
instance: `${host}:${port}${path}`,
|
|
101
|
+
method: req.method,
|
|
102
|
+
status_code: res.statusCode,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
return next();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Metrics endpoint
|
|
109
|
+
app.get(`${path === '/' ? '' : path}/metrics`, async (req, res) => {
|
|
110
|
+
res.set('Content-Type', promRegister.contentType);
|
|
111
|
+
return res.end(await promRegister.metrics());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Logging, Compression, and Body Parsers
|
|
115
|
+
app.use(loggerMiddleware(import.meta));
|
|
116
|
+
// Compression filter logic is correctly inlined here
|
|
117
|
+
app.use(compression({ filter: (req, res) => !req.headers['x-no-compression'] && compression.filter(req, res) }));
|
|
118
|
+
app.use(express.json({ limit: '100MB' }));
|
|
119
|
+
app.use(express.urlencoded({ extended: true, limit: '20MB' }));
|
|
120
|
+
app.use(fileUpload());
|
|
121
|
+
|
|
122
|
+
if (process.env.NODE_ENV === 'development') app.set('json spaces', 2);
|
|
123
|
+
|
|
124
|
+
// Language handling middleware
|
|
125
|
+
app.use((req, res, next) => {
|
|
126
|
+
const lang = req.headers['accept-language'] || 'en';
|
|
127
|
+
req.lang = typeof lang === 'string' && lang.toLowerCase().match('es') ? 'es' : 'en';
|
|
128
|
+
return next();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Static file serving
|
|
132
|
+
app.use('/', express.static(directory ? directory : `.${rootHostPath}`));
|
|
133
|
+
|
|
134
|
+
// Swagger path definition
|
|
135
|
+
const swaggerJsonPath = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
|
|
136
|
+
const swaggerPath = `${path === '/' ? `/api-docs` : `${path}/api-docs`}`;
|
|
137
|
+
|
|
138
|
+
// Flag swagger requests before security middleware
|
|
139
|
+
if (fs.existsSync(swaggerJsonPath)) {
|
|
140
|
+
app.use(swaggerPath, (req, res, next) => {
|
|
141
|
+
res.locals.isSwagger = true;
|
|
142
|
+
next();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Security and CORS
|
|
147
|
+
applySecurity(app, {
|
|
148
|
+
origin: (origin, callback) => {
|
|
149
|
+
// Use devApiPort if provided to calculate the allowed development CORS origin
|
|
150
|
+
const devOrigin =
|
|
151
|
+
apis && process.env.NODE_ENV === 'development' && devApiPort ? [`http://localhost:${devApiPort}`] : [];
|
|
152
|
+
|
|
153
|
+
const allowedOrigins = origins.concat(devOrigin);
|
|
154
|
+
|
|
155
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
156
|
+
callback(null, true);
|
|
157
|
+
} else {
|
|
158
|
+
callback(new Error('Not allowed by CORS'));
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Handle redirection-only instances
|
|
164
|
+
if (redirect) {
|
|
165
|
+
app.use((req, res, next) => {
|
|
166
|
+
if (process.env.NODE_ENV === 'production' && !req.url.startsWith(`/.well-known/acme-challenge`)) {
|
|
167
|
+
return res.status(302).redirect(redirectTarget + req.url);
|
|
168
|
+
}
|
|
169
|
+
return next();
|
|
170
|
+
});
|
|
171
|
+
await UnderpostStartUp.API.listenPortController(app, port, runningData);
|
|
172
|
+
return { portsUsed };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Create HTTP server for regular instances (required for WebSockets)
|
|
176
|
+
const server = createServer({}, app);
|
|
177
|
+
if (peer) portsUsed++; // Peer server uses one additional port
|
|
178
|
+
|
|
179
|
+
if (!apiBaseHost) {
|
|
180
|
+
// Swagger UI setup
|
|
181
|
+
if (fs.existsSync(swaggerJsonPath)) {
|
|
182
|
+
const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
|
|
183
|
+
// Reusing swaggerPath defined outside, removing unnecessary redeclaration
|
|
184
|
+
app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Database and Valkey connections
|
|
188
|
+
if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
|
|
189
|
+
if (valkey) await createValkeyConnection({ host, path }, valkey);
|
|
190
|
+
|
|
191
|
+
// Mailer setup
|
|
192
|
+
if (mailer) {
|
|
193
|
+
const mailerSsrConf = confSSR[getCapVariableName(client)];
|
|
194
|
+
await MailerProvider.load({
|
|
195
|
+
id: `${host}${path}`,
|
|
196
|
+
meta: `mailer-${host}${path}`,
|
|
197
|
+
host,
|
|
198
|
+
path,
|
|
199
|
+
...mailer,
|
|
200
|
+
templates: mailerSsrConf ? mailerSsrConf.mailer : {},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// API router loading
|
|
205
|
+
if (apis && apis.length > 0) {
|
|
206
|
+
const authMiddleware = authMiddlewareFactory({ host, path });
|
|
207
|
+
const apiPath = `${path === '/' ? '' : path}/${process.env.BASE_API}`;
|
|
208
|
+
for (const api of apis) {
|
|
209
|
+
logger.info(`Build api server`, `${host}${apiPath}/${api}`);
|
|
210
|
+
const { ApiRouter } = await import(`../../api/${api}/${api}.router.js`);
|
|
211
|
+
const router = ApiRouter({ host, path, apiPath, mailer, db, authMiddleware, origins });
|
|
212
|
+
app.use(`${apiPath}/${api}`, router);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// WebSocket server setup
|
|
217
|
+
if (ws) {
|
|
218
|
+
const { createIoServer } = await import(`../../ws/${ws}/${ws}.ws.server.js`);
|
|
219
|
+
const { options, meta } = await createIoServer(server, { host, path, db, port, origins });
|
|
220
|
+
|
|
221
|
+
// Listen on the main port for the WS server
|
|
222
|
+
await UnderpostStartUp.API.listenPortController(UnderpostStartUp.API.listenServerFactory(), port, {
|
|
223
|
+
runtime: 'nodejs',
|
|
224
|
+
client: null,
|
|
225
|
+
host,
|
|
226
|
+
path: options.path,
|
|
227
|
+
meta,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Peer server setup
|
|
232
|
+
if (peer) {
|
|
233
|
+
const peerPort = newInstance(port + portsUsed); // portsUsed is 1 here
|
|
234
|
+
const { options, meta, peerServer } = await createPeerServer({
|
|
235
|
+
port: peerPort,
|
|
236
|
+
devPort: port,
|
|
237
|
+
origins,
|
|
238
|
+
host,
|
|
239
|
+
path,
|
|
240
|
+
});
|
|
241
|
+
await UnderpostStartUp.API.listenPortController(peerServer, peerPort, {
|
|
242
|
+
runtime: 'nodejs',
|
|
243
|
+
client: null,
|
|
244
|
+
host,
|
|
245
|
+
path: options.path,
|
|
246
|
+
meta,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// SSR middleware loading
|
|
252
|
+
const ssr = await ssrMiddlewareFactory({ app, directory, rootHostPath, path });
|
|
253
|
+
for (const [_, ssrMiddleware] of Object.entries(ssr)) app.use(ssrMiddleware);
|
|
254
|
+
|
|
255
|
+
// Start listening on the main port
|
|
256
|
+
await UnderpostStartUp.API.listenPortController(server, port, runningData);
|
|
257
|
+
|
|
258
|
+
return { portsUsed };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default new ExpressService();
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exported singleton instance of the LamppService class.
|
|
3
|
+
* This object is used to interact with the Lampp configuration and service.
|
|
4
|
+
* @module src/runtime/lampp/Lampp.js
|
|
5
|
+
* @namespace LamppService
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import fs from 'fs-extra';
|
|
2
9
|
import { getRootDirectory, shellCd, shellExec } from '../../server/process.js';
|
|
3
10
|
import { loggerFactory } from '../../server/logger.js';
|
|
@@ -9,12 +16,14 @@ const logger = loggerFactory(import.meta);
|
|
|
9
16
|
* @description Provides utilities for managing the XAMPP (Lampp) service on Linux,
|
|
10
17
|
* including initialization, router configuration, and virtual host creation.
|
|
11
18
|
* It manages the server's configuration files and controls the service process.
|
|
19
|
+
* @memberof LamppService
|
|
12
20
|
*/
|
|
13
21
|
class LamppService {
|
|
14
22
|
/**
|
|
15
23
|
* @private
|
|
16
24
|
* @type {string | undefined}
|
|
17
25
|
* @description Stores the accumulated Apache virtual host configuration (router definition).
|
|
26
|
+
* @memberof LamppService
|
|
18
27
|
*/
|
|
19
28
|
router;
|
|
20
29
|
|
|
@@ -22,12 +31,15 @@ class LamppService {
|
|
|
22
31
|
* @public
|
|
23
32
|
* @type {number[]}
|
|
24
33
|
* @description A list of ports currently configured and listened to by the Lampp service.
|
|
34
|
+
* @memberof LamppService
|
|
25
35
|
*/
|
|
26
36
|
ports;
|
|
27
37
|
|
|
28
38
|
/**
|
|
29
39
|
* Creates an instance of LamppService.
|
|
30
40
|
* Initializes the router configuration and ports list.
|
|
41
|
+
* @method constructor
|
|
42
|
+
* @memberof LamppService
|
|
31
43
|
*/
|
|
32
44
|
constructor() {
|
|
33
45
|
this.router = undefined;
|
|
@@ -37,8 +49,11 @@ class LamppService {
|
|
|
37
49
|
/**
|
|
38
50
|
* Checks if the XAMPP (Lampp) service appears to be installed based on the presence of its main configuration file.
|
|
39
51
|
*
|
|
52
|
+
* @method enabled
|
|
40
53
|
* @memberof LamppService
|
|
41
54
|
* @returns {boolean} True if the configuration file exists, indicating Lampp is likely installed.
|
|
55
|
+
* @throws {Error} If the configuration file does not exist.
|
|
56
|
+
* @memberof LamppService
|
|
42
57
|
*/
|
|
43
58
|
enabled() {
|
|
44
59
|
return fs.existsSync('/opt/lampp/etc/httpd.conf');
|
|
@@ -49,10 +64,11 @@ class LamppService {
|
|
|
49
64
|
* This method configures virtual hosts, disables default ports (80/443) in the main config
|
|
50
65
|
* to avoid conflicts, and starts or stops the service using shell commands.
|
|
51
66
|
*
|
|
52
|
-
* @
|
|
67
|
+
* @method initService
|
|
53
68
|
* @param {object} [options={daemon: false}] - Options for service initialization.
|
|
54
69
|
* @param {boolean} [options.daemon=false] - Flag to indicate if the service should be run as a daemon (currently unused in logic).
|
|
55
70
|
* @returns {Promise<void>}
|
|
71
|
+
* @memberof LamppService
|
|
56
72
|
*/
|
|
57
73
|
async initService(options = { daemon: false }) {
|
|
58
74
|
let cmd;
|
|
@@ -117,9 +133,10 @@ class LamppService {
|
|
|
117
133
|
* Appends new Apache VirtualHost configuration content to the internal router string.
|
|
118
134
|
* If a router config file exists from a previous run, it loads it first.
|
|
119
135
|
*
|
|
120
|
-
* @
|
|
136
|
+
* @method appendRouter
|
|
121
137
|
* @param {string} render - The new VirtualHost configuration string to append.
|
|
122
138
|
* @returns {string} The complete, updated router configuration string.
|
|
139
|
+
* @memberof LamppService
|
|
123
140
|
*/
|
|
124
141
|
appendRouter(render) {
|
|
125
142
|
if (!this.router) {
|
|
@@ -135,7 +152,7 @@ class LamppService {
|
|
|
135
152
|
/**
|
|
136
153
|
* Resets the internal router configuration and removes the temporary configuration file.
|
|
137
154
|
*
|
|
138
|
-
* @memberof LamppService
|
|
155
|
+
* @memberof LamppService
|
|
139
156
|
* @returns {void}
|
|
140
157
|
*/
|
|
141
158
|
removeRouter() {
|
|
@@ -148,8 +165,9 @@ class LamppService {
|
|
|
148
165
|
* This includes downloading the installer, running it, and setting up initial configurations.
|
|
149
166
|
* Only runs on the 'linux' platform.
|
|
150
167
|
*
|
|
151
|
-
* @
|
|
168
|
+
* @method install
|
|
152
169
|
* @returns {Promise<void>}
|
|
170
|
+
* @memberof LamppService
|
|
153
171
|
*/
|
|
154
172
|
async install() {
|
|
155
173
|
if (process.platform === 'linux') {
|
|
@@ -199,7 +217,7 @@ class LamppService {
|
|
|
199
217
|
* Creates and appends a new Apache VirtualHost entry to the router configuration for a web application.
|
|
200
218
|
* The router is then applied by calling {@link LamppService#initService}.
|
|
201
219
|
*
|
|
202
|
-
* @
|
|
220
|
+
* @method createApp
|
|
203
221
|
* @param {object} options - Configuration options for the new web application.
|
|
204
222
|
* @param {number} options.port - The port the VirtualHost should listen on.
|
|
205
223
|
* @param {string} options.host - The ServerName/host for the VirtualHost.
|
|
@@ -210,6 +228,7 @@ class LamppService {
|
|
|
210
228
|
* @param {string} [options.redirectTarget] - The target URL for redirection.
|
|
211
229
|
* @param {boolean} [options.resetRouter] - If true, clears the existing router configuration before appending the new one.
|
|
212
230
|
* @returns {{disabled: boolean}} An object indicating if the service is disabled.
|
|
231
|
+
* @memberof LamppService
|
|
213
232
|
*/
|
|
214
233
|
createApp({ port, host, path, directory, rootHostPath, redirect, redirectTarget, resetRouter }) {
|
|
215
234
|
if (!this.enabled()) return { disabled: true };
|
|
@@ -262,13 +281,15 @@ Listen ${port}
|
|
|
262
281
|
}
|
|
263
282
|
|
|
264
283
|
/**
|
|
265
|
-
* @namespace LamppService
|
|
266
284
|
* @description Exported singleton instance of the LamppService class.
|
|
267
285
|
* This object is used to interact with the Lampp configuration and service.
|
|
286
|
+
* @memberof LamppService
|
|
268
287
|
* @type {LamppService}
|
|
269
288
|
*/
|
|
270
289
|
const Lampp = new LamppService();
|
|
271
290
|
|
|
291
|
+
export { Lampp };
|
|
292
|
+
|
|
272
293
|
// -- helper info --
|
|
273
294
|
|
|
274
295
|
// ERR too many redirects:
|
|
@@ -320,5 +341,3 @@ const Lampp = new LamppService();
|
|
|
320
341
|
// RewriteCond %{HTTP_HOST} ^www\. [NC]
|
|
321
342
|
// RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
|
|
322
343
|
// RewriteRule ^ https://%1%{REQUEST_URI} [L,NE,R=301]
|
|
323
|
-
|
|
324
|
-
export { Lampp };
|
package/src/server/auth.js
CHANGED
|
@@ -110,16 +110,22 @@ function jwtIssuerAudienceFactory(options = { host: '', path: '' }) {
|
|
|
110
110
|
* @param {object} [options={}] Additional JWT sign options.
|
|
111
111
|
* @param {string} options.host The host name.
|
|
112
112
|
* @param {string} options.path The path name.
|
|
113
|
-
* @param {number}
|
|
113
|
+
* @param {number} accessExpireMinutes The access token expiration in minutes.
|
|
114
|
+
* @param {number} refreshExpireMinutes The refresh token expiration in minutes.
|
|
114
115
|
* @returns {string} The signed JWT.
|
|
115
116
|
* @throws {Error} If JWT key is not configured.
|
|
116
117
|
* @memberof Auth
|
|
117
118
|
*/
|
|
118
|
-
function jwtSign(
|
|
119
|
+
function jwtSign(
|
|
120
|
+
payload,
|
|
121
|
+
options = { host: '', path: '' },
|
|
122
|
+
accessExpireMinutes = process.env.ACCESS_EXPIRE_MINUTES,
|
|
123
|
+
refreshExpireMinutes = process.env.REFRESH_EXPIRE_MINUTES,
|
|
124
|
+
) {
|
|
119
125
|
const { issuer, audience } = jwtIssuerAudienceFactory(options);
|
|
120
126
|
const signOptions = {
|
|
121
127
|
algorithm: config.jwtAlgorithm,
|
|
122
|
-
expiresIn: `${
|
|
128
|
+
expiresIn: `${accessExpireMinutes}m`,
|
|
123
129
|
issuer,
|
|
124
130
|
audience,
|
|
125
131
|
};
|
|
@@ -128,7 +134,11 @@ function jwtSign(payload, options = { host: '', path: '' }, expireMinutes = proc
|
|
|
128
134
|
|
|
129
135
|
if (!process.env.JWT_SECRET) throw new Error('JWT key not configured');
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
// Add refreshExpiresAt to the payload, which is the same as the token's own expiry.
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
payload.refreshExpiresAt = now + refreshExpireMinutes * 60 * 1000;
|
|
140
|
+
|
|
141
|
+
logger.info('JWT signed', { payload, signOptions, accessExpireMinutes, refreshExpireMinutes });
|
|
132
142
|
|
|
133
143
|
return jwt.sign(payload, process.env.JWT_SECRET, signOptions);
|
|
134
144
|
}
|
|
@@ -170,6 +180,14 @@ const getBearerToken = (req) => {
|
|
|
170
180
|
return '';
|
|
171
181
|
};
|
|
172
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Checks if the request is a refresh token request.
|
|
185
|
+
* @param {import('express').Request} req The Express request object.
|
|
186
|
+
* @returns {boolean} True if the request is a refresh token request, false otherwise.
|
|
187
|
+
* @memberof Auth
|
|
188
|
+
*/
|
|
189
|
+
const isRefreshTokenReq = (req) => req.method === 'GET' && req.params.id === 'auth';
|
|
190
|
+
|
|
173
191
|
// ---------- Middleware ----------
|
|
174
192
|
/**
|
|
175
193
|
* Creates a middleware to authenticate requests using a JWT Bearer token.
|
|
@@ -237,10 +255,7 @@ const authMiddlewareFactory = (options = { host: '', path: '' }) => {
|
|
|
237
255
|
return res.status(401).json({ status: 'error', message: 'unauthorized: host or path mismatch' });
|
|
238
256
|
}
|
|
239
257
|
|
|
240
|
-
|
|
241
|
-
const isRefreshTokenReq = req.method === 'GET' && req.params.id === 'auth';
|
|
242
|
-
|
|
243
|
-
if (!isRefreshTokenReq && session.expiresAt < new Date()) {
|
|
258
|
+
if (!isRefreshTokenReq(req) && session.expiresAt < new Date()) {
|
|
244
259
|
return res.status(401).json({ status: 'error', message: 'unauthorized: session expired' });
|
|
245
260
|
}
|
|
246
261
|
}
|
|
@@ -342,11 +357,11 @@ const cookieOptionsFactory = (req) => {
|
|
|
342
357
|
const reqIsSecure = Boolean(req.secure || forwardedProto.split(',')[0] === 'https');
|
|
343
358
|
|
|
344
359
|
// secure must be true for SameSite=None to work across sites
|
|
345
|
-
const secure = isProduction ? reqIsSecure :
|
|
360
|
+
const secure = isProduction ? reqIsSecure : req.protocol === 'https';
|
|
346
361
|
const sameSite = secure ? 'None' : 'Lax';
|
|
347
362
|
|
|
348
363
|
// Safe parse of maxAge minutes
|
|
349
|
-
const minutes = Number.parseInt(process.env.
|
|
364
|
+
const minutes = Number.parseInt(process.env.ACCESS_EXPIRE_MINUTES, 10);
|
|
350
365
|
const maxAge = Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : undefined;
|
|
351
366
|
|
|
352
367
|
const opts = {
|
|
@@ -399,6 +414,48 @@ async function createSessionAndUserToken(user, User, req, res, options = { host:
|
|
|
399
414
|
return { jwtid };
|
|
400
415
|
}
|
|
401
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Removes a session by its ID for a given user.
|
|
419
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
420
|
+
* @param {string} userId The ID of the user.
|
|
421
|
+
* @param {string} sessionId The ID of the session to remove.
|
|
422
|
+
* @returns {Promise<void>}
|
|
423
|
+
* @memberof Auth
|
|
424
|
+
*/
|
|
425
|
+
async function removeSession(User, userId, sessionId) {
|
|
426
|
+
return await User.updateOne({ _id: userId }, { $pull: { activeSessions: { _id: sessionId } } });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Logs out a user session by removing it from the database and clearing the refresh token cookie.
|
|
431
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
432
|
+
* @param {import('express').Request} req The Express request object.
|
|
433
|
+
* @param {import('express').Response} res The Express response object.
|
|
434
|
+
* @returns {Promise<boolean>} True if a session was found and removed, false otherwise.
|
|
435
|
+
* @memberof Auth
|
|
436
|
+
*/
|
|
437
|
+
async function logoutSession(User, req, res) {
|
|
438
|
+
const refreshToken = req.cookies?.refreshToken;
|
|
439
|
+
|
|
440
|
+
if (!refreshToken) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const user = await User.findOne({ 'activeSessions.tokenHash': refreshToken });
|
|
445
|
+
|
|
446
|
+
if (!user) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const session = user.activeSessions.find((s) => s.tokenHash === refreshToken);
|
|
451
|
+
|
|
452
|
+
const result = await removeSession(User, user._id, session._id);
|
|
453
|
+
|
|
454
|
+
res.clearCookie('refreshToken', { path: '/' });
|
|
455
|
+
|
|
456
|
+
return result.modifiedCount > 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
402
459
|
/**
|
|
403
460
|
* Create user and immediate session + access token
|
|
404
461
|
* @param {import('express').Request} req The Express request object.
|
|
@@ -470,12 +527,10 @@ async function refreshSessionAndToken(req, res, User, options = { host: '', path
|
|
|
470
527
|
}
|
|
471
528
|
|
|
472
529
|
// Check expiry
|
|
473
|
-
if (session.expiresAt && session.expiresAt < new Date()) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
res.clearCookie('refreshToken', { path: '/' });
|
|
478
|
-
throw new Error('Refresh token expired');
|
|
530
|
+
if (!isRefreshTokenReq(req) && session.expiresAt && session.expiresAt < new Date()) {
|
|
531
|
+
const result = await removeSession(User, user._id, session._id);
|
|
532
|
+
if (result) throw new Error('Refresh token expired');
|
|
533
|
+
else throw new Error('Session not found');
|
|
479
534
|
}
|
|
480
535
|
|
|
481
536
|
// Rotate: generate new token, update stored hash and metadata
|
|
@@ -646,5 +701,8 @@ export {
|
|
|
646
701
|
createSessionAndUserToken,
|
|
647
702
|
createUserAndSession,
|
|
648
703
|
refreshSessionAndToken,
|
|
704
|
+
logoutSession,
|
|
705
|
+
removeSession,
|
|
649
706
|
applySecurity,
|
|
707
|
+
isRefreshTokenReq,
|
|
650
708
|
};
|
package/src/server/runtime.js
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace Runtime
|
|
3
|
+
* @description The main runtime orchestrator responsible for reading configuration,
|
|
4
|
+
* initializing services (Prometheus, Ports, DB, Mailer), and building the
|
|
5
|
+
* specific server runtime for each host/path (e.g., nodejs, lampp).
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import fs from 'fs-extra';
|
|
2
|
-
import express from 'express';
|
|
3
9
|
import dotenv from 'dotenv';
|
|
4
|
-
import fileUpload from 'express-fileupload';
|
|
5
|
-
import swaggerUi from 'swagger-ui-express';
|
|
6
10
|
import * as promClient from 'prom-client';
|
|
7
|
-
import compression from 'compression';
|
|
8
11
|
|
|
9
12
|
import UnderpostStartUp from './start.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { getCapVariableName, newInstance } from '../client/components/core/CommonJs.js';
|
|
13
|
-
import { MailerProvider } from '../mailer/MailerProvider.js';
|
|
14
|
-
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
15
|
-
import { createPeerServer } from './peer.js';
|
|
13
|
+
import { loggerFactory } from './logger.js';
|
|
14
|
+
import { newInstance } from '../client/components/core/CommonJs.js';
|
|
16
15
|
import { Lampp } from '../runtime/lampp/Lampp.js';
|
|
17
|
-
import { createValkeyConnection } from './valkey.js';
|
|
18
|
-
import { applySecurity, authMiddlewareFactory } from './auth.js';
|
|
19
16
|
import { getInstanceContext } from './conf.js';
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
import ExpressService from '../runtime/express/Express.js';
|
|
21
19
|
|
|
22
20
|
dotenv.config();
|
|
23
21
|
|
|
24
22
|
const logger = loggerFactory(import.meta);
|
|
25
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Reads server configurations, sets up Prometheus metrics, and iterates through
|
|
26
|
+
* all defined hosts and paths to build and start the corresponding runtime instances.
|
|
27
|
+
*
|
|
28
|
+
* @memberof Runtime
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
26
31
|
const buildRuntime = async () => {
|
|
27
32
|
const deployId = process.env.DEPLOY_ID;
|
|
28
33
|
|
|
34
|
+
// 1. Initialize Prometheus Metrics
|
|
29
35
|
const collectDefaultMetrics = promClient.collectDefaultMetrics;
|
|
30
36
|
collectDefaultMetrics();
|
|
31
37
|
|
|
@@ -38,12 +44,17 @@ const buildRuntime = async () => {
|
|
|
38
44
|
const requestCounter = new promClient.Counter(promCounterOption);
|
|
39
45
|
const initPort = parseInt(process.env.PORT) + 1;
|
|
40
46
|
let currentPort = initPort;
|
|
47
|
+
|
|
48
|
+
// 2. Load Configuration
|
|
41
49
|
const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
|
|
42
50
|
const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
|
|
43
51
|
const singleReplicaHosts = [];
|
|
52
|
+
|
|
53
|
+
// 3. Iterate through hosts and paths
|
|
44
54
|
for (const host of Object.keys(confServer)) {
|
|
45
55
|
if (singleReplicaHosts.length > 0)
|
|
46
56
|
currentPort += singleReplicaHosts.reduce((accumulator, currentValue) => accumulator + currentValue.replicas, 0);
|
|
57
|
+
|
|
47
58
|
const rootHostPath = `/public/${host}`;
|
|
48
59
|
for (const path of Object.keys(confServer[host])) {
|
|
49
60
|
confServer[host][path].port = newInstance(currentPort);
|
|
@@ -65,6 +76,7 @@ const buildRuntime = async () => {
|
|
|
65
76
|
apiBaseHost,
|
|
66
77
|
} = confServer[host][path];
|
|
67
78
|
|
|
79
|
+
// Calculate context data
|
|
68
80
|
const { redirectTarget, singleReplicaHost } = await getInstanceContext({
|
|
69
81
|
redirect,
|
|
70
82
|
singleReplicaHosts,
|
|
@@ -91,203 +103,37 @@ const buildRuntime = async () => {
|
|
|
91
103
|
|
|
92
104
|
switch (runtime) {
|
|
93
105
|
case 'nodejs':
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// js src compression
|
|
122
|
-
app.use(compression({ filter: shouldCompress }));
|
|
123
|
-
function shouldCompress(req, res) {
|
|
124
|
-
if (req.headers['x-no-compression']) {
|
|
125
|
-
// don't compress responses with this request header
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// fallback to standard filter function
|
|
130
|
-
return compression.filter(req, res);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// parse requests of content-type - application/json
|
|
134
|
-
app.use(express.json({ limit: '100MB' }));
|
|
135
|
-
|
|
136
|
-
// parse requests of content-type - application/x-www-form-urlencoded
|
|
137
|
-
app.use(express.urlencoded({ extended: true, limit: '20MB' }));
|
|
138
|
-
|
|
139
|
-
// file upload middleware
|
|
140
|
-
app.use(fileUpload());
|
|
141
|
-
|
|
142
|
-
// json formatted response
|
|
143
|
-
if (process.env.NODE_ENV === 'development') app.set('json spaces', 2);
|
|
144
|
-
|
|
145
|
-
// lang handling middleware
|
|
146
|
-
app.use(function (req, res, next) {
|
|
147
|
-
const lang = req.headers['accept-language'] || 'en';
|
|
148
|
-
if (typeof lang === 'string' && lang.toLowerCase().match('es')) {
|
|
149
|
-
req.lang = 'es';
|
|
150
|
-
} else req.lang = 'en';
|
|
151
|
-
return next();
|
|
106
|
+
// The devApiPort is used for development CORS origin calculation
|
|
107
|
+
// It needs to account for the current port and potential peer server increment
|
|
108
|
+
const devApiPort = currentPort + (peer ? 2 : 1);
|
|
109
|
+
|
|
110
|
+
logger.info('Build nodejs server runtime', `${host}${path}:${port}`);
|
|
111
|
+
|
|
112
|
+
const { portsUsed } = await ExpressService.createApp({
|
|
113
|
+
host,
|
|
114
|
+
path,
|
|
115
|
+
port,
|
|
116
|
+
client,
|
|
117
|
+
apis,
|
|
118
|
+
origins,
|
|
119
|
+
directory,
|
|
120
|
+
ws,
|
|
121
|
+
mailer,
|
|
122
|
+
db,
|
|
123
|
+
redirect,
|
|
124
|
+
peer,
|
|
125
|
+
valkey,
|
|
126
|
+
apiBaseHost,
|
|
127
|
+
devApiPort, // Pass the dynamically calculated dev API port
|
|
128
|
+
redirectTarget,
|
|
129
|
+
rootHostPath,
|
|
130
|
+
confSSR,
|
|
131
|
+
promRequestCounter: requestCounter,
|
|
132
|
+
promRegister: promClient.register,
|
|
152
133
|
});
|
|
153
134
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
if (process.argv.includes('static')) {
|
|
157
|
-
logger.info('Build static server runtime', `${host}${path}`);
|
|
158
|
-
currentPort += 2;
|
|
159
|
-
const staticPort = newInstance(currentPort);
|
|
160
|
-
await UnderpostStartUp.API.listenPortController(app, staticPort, runningData);
|
|
161
|
-
currentPort++;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Flag swagger requests before security middleware is applied
|
|
166
|
-
const swaggerJsonPath = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
|
|
167
|
-
const swaggerPath = `${path === '/' ? `/api-docs` : `${path}/api-docs`}`;
|
|
168
|
-
if (fs.existsSync(swaggerJsonPath))
|
|
169
|
-
app.use(swaggerPath, (req, res, next) => {
|
|
170
|
-
res.locals.isSwagger = true;
|
|
171
|
-
next();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// security
|
|
175
|
-
applySecurity(app, {
|
|
176
|
-
origin: origins.concat(
|
|
177
|
-
apis && process.env.NODE_ENV === 'development' ? [`http://localhost:${currentPort + 2}`] : [],
|
|
178
|
-
),
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (redirect) {
|
|
182
|
-
app.use(function (req = express.Request, res = express.Response, next = express.NextFunction) {
|
|
183
|
-
if (process.env.NODE_ENV === 'production' && !req.url.startsWith(`/.well-known/acme-challenge`))
|
|
184
|
-
return res.status(302).redirect(redirectTarget + req.url);
|
|
185
|
-
// if (!req.url.startsWith(`/.well-known/acme-challenge`)) return res.status(302).redirect(redirect);
|
|
186
|
-
return next();
|
|
187
|
-
});
|
|
188
|
-
// app.use(
|
|
189
|
-
// '*',
|
|
190
|
-
// createProxyMiddleware({
|
|
191
|
-
// target: redirect,
|
|
192
|
-
// changeOrigin: true,
|
|
193
|
-
// }),
|
|
194
|
-
// );
|
|
195
|
-
|
|
196
|
-
await UnderpostStartUp.API.listenPortController(app, port, runningData);
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
// instance server
|
|
200
|
-
const server = createServer({}, app);
|
|
201
|
-
if (peer) currentPort++;
|
|
202
|
-
|
|
203
|
-
if (!apiBaseHost) {
|
|
204
|
-
if (fs.existsSync(swaggerJsonPath)) {
|
|
205
|
-
const swaggerInstance =
|
|
206
|
-
(swaggerDoc) =>
|
|
207
|
-
(...args) =>
|
|
208
|
-
swaggerUi.setup(swaggerDoc)(...args);
|
|
209
|
-
const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
|
|
210
|
-
const swaggerPath = `${path === '/' ? `/api-docs` : `${path}/api-docs`}`;
|
|
211
|
-
app.use(swaggerPath, swaggerUi.serve, swaggerInstance(swaggerDoc));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
|
|
215
|
-
|
|
216
|
-
// valkey server
|
|
217
|
-
if (valkey) await createValkeyConnection({ host, path }, valkey);
|
|
218
|
-
|
|
219
|
-
if (mailer) {
|
|
220
|
-
const mailerSsrConf = confSSR[getCapVariableName(client)];
|
|
221
|
-
await MailerProvider.load({
|
|
222
|
-
id: `${host}${path}`,
|
|
223
|
-
meta: `mailer-${host}${path}`,
|
|
224
|
-
host,
|
|
225
|
-
path,
|
|
226
|
-
...mailer,
|
|
227
|
-
templates: mailerSsrConf ? mailerSsrConf.mailer : {},
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (apis && apis.length > 0) {
|
|
231
|
-
const authMiddleware = authMiddlewareFactory({ host, path });
|
|
232
|
-
const apiPath = `${path === '/' ? '' : path}/${process.env.BASE_API}`;
|
|
233
|
-
for (const api of apis)
|
|
234
|
-
await (async () => {
|
|
235
|
-
logger.info(`Build api server`, `${host}${apiPath}/${api}`);
|
|
236
|
-
const { ApiRouter } = await import(`../api/${api}/${api}.router.js`);
|
|
237
|
-
const router = ApiRouter({ host, path, apiPath, mailer, db, authMiddleware, origins });
|
|
238
|
-
// router.use(cors({ origin: origins }));
|
|
239
|
-
// logger.info('Load api router', { host, path: apiPath, api });
|
|
240
|
-
app.use(`${apiPath}/${api}`, router);
|
|
241
|
-
})();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (ws)
|
|
245
|
-
await (async () => {
|
|
246
|
-
const { createIoServer } = await import(`../ws/${ws}/${ws}.ws.server.js`);
|
|
247
|
-
// logger.info('Load socket.io ws router', { host, ws });
|
|
248
|
-
// start socket.io
|
|
249
|
-
const { options, meta } = await createIoServer(server, {
|
|
250
|
-
host,
|
|
251
|
-
path,
|
|
252
|
-
db,
|
|
253
|
-
port,
|
|
254
|
-
origins,
|
|
255
|
-
});
|
|
256
|
-
await UnderpostStartUp.API.listenPortController(UnderpostStartUp.API.listenServerFactory(), port, {
|
|
257
|
-
runtime: 'nodejs',
|
|
258
|
-
client: null,
|
|
259
|
-
host,
|
|
260
|
-
path: options.path,
|
|
261
|
-
meta,
|
|
262
|
-
});
|
|
263
|
-
})();
|
|
264
|
-
|
|
265
|
-
if (peer) {
|
|
266
|
-
const peerPort = newInstance(currentPort);
|
|
267
|
-
const { options, meta, peerServer } = await createPeerServer({
|
|
268
|
-
port: peerPort,
|
|
269
|
-
devPort: port,
|
|
270
|
-
origins,
|
|
271
|
-
host,
|
|
272
|
-
path,
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await UnderpostStartUp.API.listenPortController(peerServer, peerPort, {
|
|
276
|
-
runtime: 'nodejs',
|
|
277
|
-
client: null,
|
|
278
|
-
host,
|
|
279
|
-
path: options.path,
|
|
280
|
-
meta,
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// load ssr
|
|
286
|
-
const ssr = await ssrMiddlewareFactory({ app, directory, rootHostPath, path });
|
|
287
|
-
for (const [_, ssrMiddleware] of Object.entries(ssr)) app.use(ssrMiddleware);
|
|
288
|
-
|
|
289
|
-
await UnderpostStartUp.API.listenPortController(server, port, runningData);
|
|
290
|
-
|
|
135
|
+
// Increment currentPort by any additional ports used by the service (e.g., PeerServer port)
|
|
136
|
+
currentPort += portsUsed;
|
|
291
137
|
break;
|
|
292
138
|
|
|
293
139
|
case 'lampp':
|