underpost 2.8.881 → 2.8.883

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.
Files changed (36) hide show
  1. package/.github/workflows/release.cd.yml +1 -2
  2. package/README.md +50 -36
  3. package/bin/db.js +1 -4
  4. package/cli.md +86 -86
  5. package/conf.js +1 -0
  6. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  7. package/manifests/deployment/dd-test-development/deployment.yaml +6 -6
  8. package/manifests/maas/device-scan.sh +1 -1
  9. package/package.json +1 -1
  10. package/src/api/document/document.service.js +9 -1
  11. package/src/cli/repository.js +2 -0
  12. package/src/client/components/core/Auth.js +258 -89
  13. package/src/client/components/core/BtnIcon.js +10 -1
  14. package/src/client/components/core/CssCore.js +36 -27
  15. package/src/client/components/core/Docs.js +188 -85
  16. package/src/client/components/core/LoadingAnimation.js +5 -10
  17. package/src/client/components/core/Modal.js +262 -120
  18. package/src/client/components/core/ObjectLayerEngine.js +154 -158
  19. package/src/client/components/core/Panel.js +2 -0
  20. package/src/client/components/core/PanelForm.js +94 -60
  21. package/src/client/components/core/Router.js +15 -15
  22. package/src/client/components/core/ToolTip.js +83 -19
  23. package/src/client/components/core/Translate.js +1 -1
  24. package/src/client/components/core/VanillaJs.js +4 -3
  25. package/src/client/components/core/windowGetDimensions.js +202 -0
  26. package/src/client/components/default/MenuDefault.js +11 -0
  27. package/src/client/ssr/Render.js +1 -1
  28. package/src/index.js +1 -1
  29. package/src/runtime/lampp/Lampp.js +253 -128
  30. package/src/server/auth.js +68 -17
  31. package/src/server/crypto.js +195 -76
  32. package/src/server/peer.js +47 -5
  33. package/src/server/process.js +85 -1
  34. package/src/server/runtime.js +13 -32
  35. package/test/crypto.test.js +117 -0
  36. package/src/runtime/xampp/Xampp.js +0 -83
@@ -4,120 +4,230 @@ import { loggerFactory } from '../../server/logger.js';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const Lampp = {
8
- ports: [],
9
- initService: async function (options = { daemon: false }) {
7
+ /**
8
+ * @class LamppService
9
+ * @description Provides utilities for managing the XAMPP (Lampp) service on Linux,
10
+ * including initialization, router configuration, and virtual host creation.
11
+ * It manages the server's configuration files and controls the service process.
12
+ */
13
+ class LamppService {
14
+ /**
15
+ * @private
16
+ * @type {string | undefined}
17
+ * @description Stores the accumulated Apache virtual host configuration (router definition).
18
+ */
19
+ router;
20
+
21
+ /**
22
+ * @public
23
+ * @type {number[]}
24
+ * @description A list of ports currently configured and listened to by the Lampp service.
25
+ */
26
+ ports;
27
+
28
+ /**
29
+ * Creates an instance of LamppService.
30
+ * Initializes the router configuration and ports list.
31
+ */
32
+ constructor() {
33
+ this.router = undefined;
34
+ this.ports = [];
35
+ }
36
+
37
+ /**
38
+ * Checks if the XAMPP (Lampp) service appears to be installed based on the presence of its main configuration file.
39
+ *
40
+ * @memberof LamppService
41
+ * @returns {boolean} True if the configuration file exists, indicating Lampp is likely installed.
42
+ */
43
+ enabled() {
44
+ return fs.existsSync('/opt/lampp/etc/httpd.conf');
45
+ }
46
+
47
+ /**
48
+ * Initializes or restarts the Lampp Apache service.
49
+ * This method configures virtual hosts, disables default ports (80/443) in the main config
50
+ * to avoid conflicts, and starts or stops the service using shell commands.
51
+ *
52
+ * @memberof LamppService.prototype
53
+ * @param {object} [options={daemon: false}] - Options for service initialization.
54
+ * @param {boolean} [options.daemon=false] - Flag to indicate if the service should be run as a daemon (currently unused in logic).
55
+ * @returns {Promise<void>}
56
+ */
57
+ async initService(options = { daemon: false }) {
10
58
  let cmd;
11
- // linux
12
- // /opt/lampp/apache2/conf/httpd.conf
59
+
60
+ // 1. Write the current virtual host router configuration
13
61
  fs.writeFileSync(`/opt/lampp/etc/extra/httpd-vhosts.conf`, this.router || '', 'utf8');
62
+
63
+ // 2. Ensure the vhosts file is included in the main httpd.conf
64
+ const httpdConfPath = `/opt/lampp/etc/httpd.conf`;
14
65
  fs.writeFileSync(
15
- `/opt/lampp/etc/httpd.conf`,
66
+ httpdConfPath,
16
67
  fs
17
- .readFileSync(`/opt/lampp/etc/httpd.conf`, 'utf8')
68
+ .readFileSync(httpdConfPath, 'utf8')
18
69
  .replace(`#Include etc/extra/httpd-vhosts.conf`, `Include etc/extra/httpd-vhosts.conf`),
19
70
  'utf8',
20
71
  );
21
72
 
73
+ // 3. Stop the service before making port changes
22
74
  cmd = `sudo /opt/lampp/lampp stop`;
23
- if (!fs.readFileSync(`/opt/lampp/etc/httpd.conf`, 'utf8').match(`# Listen 80`))
75
+ shellExec(cmd);
76
+
77
+ // 4. Comment out default port Listen directives (80 and 443) to prevent conflicts
78
+ // Modify httpd.conf (port 80)
79
+ if (!fs.readFileSync(httpdConfPath, 'utf8').match(/# Listen 80/))
24
80
  fs.writeFileSync(
25
- `/opt/lampp/etc/httpd.conf`,
26
- fs.readFileSync(`/opt/lampp/etc/httpd.conf`, 'utf8').replace(`Listen 80`, `# Listen 80`),
81
+ httpdConfPath,
82
+ fs.readFileSync(httpdConfPath, 'utf8').replace(`Listen 80`, `# Listen 80`),
27
83
  'utf8',
28
84
  );
29
- if (!fs.readFileSync(`/opt/lampp/etc/extra/httpd-ssl.conf`, 'utf8').match(`# Listen 443`))
85
+
86
+ // Modify httpd-ssl.conf (port 443)
87
+ const httpdSslConfPath = `/opt/lampp/etc/extra/httpd-ssl.conf`;
88
+ if (fs.existsSync(httpdSslConfPath) && !fs.readFileSync(httpdSslConfPath, 'utf8').match(/# Listen 443/))
30
89
  fs.writeFileSync(
31
- `/opt/lampp/etc/extra/httpd-ssl.conf`,
32
- fs.readFileSync(`/opt/lampp/etc/extra/httpd-ssl.conf`, 'utf8').replace(`Listen 443`, `# Listen 443`),
90
+ httpdSslConfPath,
91
+ fs.readFileSync(httpdSslConfPath, 'utf8').replace(`Listen 443`, `# Listen 443`),
33
92
  'utf8',
34
93
  );
35
- if (!fs.readFileSync(`/opt/lampp/lampp`, 'utf8').match(`testport 443 && false`))
94
+
95
+ // 5. Modify the lampp startup script to bypass port checking for 80 and 443
96
+ const lamppScriptPath = `/opt/lampp/lampp`;
97
+ if (!fs.readFileSync(lamppScriptPath, 'utf8').match(/testport 443 && false/))
36
98
  fs.writeFileSync(
37
- `/opt/lampp/lampp`,
38
- fs.readFileSync(`/opt/lampp/lampp`, 'utf8').replace(`testport 443`, `testport 443 && false`),
99
+ lamppScriptPath,
100
+ fs.readFileSync(lamppScriptPath, 'utf8').replace(`testport 443`, `testport 443 && false`),
39
101
  'utf8',
40
102
  );
41
- if (!fs.readFileSync(`/opt/lampp/lampp`, 'utf8').match(`testport 80 && false`))
103
+ if (!fs.readFileSync(lamppScriptPath, 'utf8').match(/testport 80 && false/))
42
104
  fs.writeFileSync(
43
- `/opt/lampp/lampp`,
44
- fs.readFileSync(`/opt/lampp/lampp`, 'utf8').replace(`testport 80`, `testport 80 && false`),
105
+ lamppScriptPath,
106
+ fs.readFileSync(lamppScriptPath, 'utf8').replace(`testport 80`, `testport 80 && false`),
45
107
  'utf8',
46
108
  );
47
109
 
48
- shellExec(cmd);
110
+ // 6. Start the service
49
111
  cmd = `sudo /opt/lampp/lampp start`;
50
112
  if (this.router) fs.writeFileSync(`./tmp/lampp-router.conf`, this.router, 'utf-8');
51
113
  shellExec(cmd);
52
- },
53
- enabled: () => fs.existsSync(`/opt/lampp/etc/httpd.conf`),
54
- appendRouter: function (render) {
114
+ }
115
+
116
+ /**
117
+ * Appends new Apache VirtualHost configuration content to the internal router string.
118
+ * If a router config file exists from a previous run, it loads it first.
119
+ *
120
+ * @memberof LamppService.prototype
121
+ * @param {string} render - The new VirtualHost configuration string to append.
122
+ * @returns {string} The complete, updated router configuration string.
123
+ */
124
+ appendRouter(render) {
55
125
  if (!this.router) {
56
- if (fs.existsSync(`./tmp/lampp-router.conf`))
57
- return (this.router = fs.readFileSync(`./tmp/lampp-router.conf`, 'utf-8')) + render;
126
+ if (fs.existsSync(`./tmp/lampp-router.conf`)) {
127
+ this.router = fs.readFileSync(`./tmp/lampp-router.conf`, 'utf-8');
128
+ return this.router + render;
129
+ }
58
130
  return (this.router = render);
59
131
  }
60
132
  return (this.router += render);
61
- },
62
- removeRouter: function () {
133
+ }
134
+
135
+ /**
136
+ * Resets the internal router configuration and removes the temporary configuration file.
137
+ *
138
+ * @memberof LamppService.prototype
139
+ * @returns {void}
140
+ */
141
+ removeRouter() {
63
142
  this.router = undefined;
64
143
  if (fs.existsSync(`./tmp/lampp-router.conf`)) fs.rmSync(`./tmp/lampp-router.conf`);
65
- },
66
- install: async function () {
67
- switch (process.platform) {
68
- case 'linux':
69
- {
70
- if (!fs.existsSync(`./engine-private/setup`)) fs.mkdirSync(`./engine-private/setup`, { recursive: true });
71
-
72
- shellCd(`./engine-private/setup`);
73
-
74
- if (!process.argv.includes(`server`)) {
75
- shellExec(
76
- `curl -Lo xampp-linux-installer.run https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/7.4.30/xampp-linux-x64-7.4.30-1-installer.run?from_af=true`,
77
- );
78
- shellExec(`sudo chmod +x xampp-linux-installer.run`);
79
- shellExec(
80
- `sudo ./xampp-linux-installer.run --mode unattended && \\` +
81
- `ln -sf /opt/lampp/lampp /usr/bin/lampp && \\` +
82
- `sed -i.bak s'/Require local/Require all granted/g' /opt/lampp/etc/extra/httpd-xampp.conf && \\` +
83
- `sed -i.bak s'/display_errors=Off/display_errors=On/g' /opt/lampp/etc/php.ini && \\` +
84
- `mkdir /opt/lampp/apache2/conf.d && \\` +
85
- `echo "IncludeOptional /opt/lampp/apache2/conf.d/*.conf" >> /opt/lampp/etc/httpd.conf && \\` +
86
- `mkdir /www && \\` +
87
- `ln -s /www /opt/lampp/htdocs`,
88
- );
89
- }
90
-
91
- if (fs.existsSync(`/opt/lampp/logs/access_log`))
92
- fs.copySync(`/opt/lampp/logs/access_log`, `/opt/lampp/logs/access.log`);
93
- if (fs.existsSync(`/opt/lampp/logs/error_log`))
94
- fs.copySync(`/opt/lampp/logs/error_log`, `/opt/lampp/logs/error.log`);
95
- if (fs.existsSync(`/opt/lampp/logs/php_error_log`))
96
- fs.copySync(`/opt/lampp/logs/php_error_log`, `/opt/lampp/logs/php_error.log`);
97
- if (fs.existsSync(`/opt/lampp/logs/ssl_request_log`))
98
- fs.copySync(`/opt/lampp/logs/ssl_request_log`, `/opt/lampp/logs/ssl_request.log`);
99
-
100
- await Lampp.initService({ daemon: true });
101
- }
102
-
103
- break;
104
-
105
- default:
106
- break;
144
+ }
145
+
146
+ /**
147
+ * Installs and configures the Lampp service on Linux.
148
+ * This includes downloading the installer, running it, and setting up initial configurations.
149
+ * Only runs on the 'linux' platform.
150
+ *
151
+ * @memberof LamppService.prototype
152
+ * @returns {Promise<void>}
153
+ */
154
+ async install() {
155
+ if (process.platform === 'linux') {
156
+ if (!fs.existsSync(`./engine-private/setup`)) fs.mkdirSync(`./engine-private/setup`, { recursive: true });
157
+
158
+ shellCd(`./engine-private/setup`);
159
+
160
+ if (!process.argv.includes(`server`)) {
161
+ // Download and run the XAMPP installer
162
+ shellExec(
163
+ `curl -Lo xampp-linux-installer.run https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/7.4.30/xampp-linux-x64-7.4.30-1-installer.run?from_af=true`,
164
+ );
165
+ shellExec(`sudo chmod +x xampp-linux-installer.run`);
166
+ shellExec(
167
+ `sudo ./xampp-linux-installer.run --mode unattended && \\` +
168
+ // Create symlink for easier access
169
+ `ln -sf /opt/lampp/lampp /usr/bin/lampp && \\` +
170
+ // Allow all access to xampp config (security measure override)
171
+ `sed -i.bak s'/Require local/Require all granted/g' /opt/lampp/etc/extra/httpd-xampp.conf && \\` +
172
+ // Enable display errors in PHP
173
+ `sed -i.bak s'/display_errors=Off/display_errors=On/g' /opt/lampp/etc/php.ini && \\` +
174
+ // Allow including custom Apache configuration files
175
+ `mkdir /opt/lampp/apache2/conf.d && \\` +
176
+ `echo "IncludeOptional /opt/lampp/apache2/conf.d/*.conf" >> /opt/lampp/etc/httpd.conf && \\` +
177
+ // Create /www directory and symlink it to htdocs
178
+ `mkdir /www && \\` +
179
+ `ln -s /www /opt/lampp/htdocs`,
180
+ );
181
+ }
182
+
183
+ // Copy log files to standard names for easier consumption
184
+ if (fs.existsSync(`/opt/lampp/logs/access_log`))
185
+ fs.copySync(`/opt/lampp/logs/access_log`, `/opt/lampp/logs/access.log`);
186
+ if (fs.existsSync(`/opt/lampp/logs/error_log`))
187
+ fs.copySync(`/opt/lampp/logs/error_log`, `/opt/lampp/logs/error.log`);
188
+ if (fs.existsSync(`/opt/lampp/logs/php_error_log`))
189
+ fs.copySync(`/opt/lampp/logs/php_error_log`, `/opt/lampp/logs/php_error.log`);
190
+ if (fs.existsSync(`/opt/lampp/logs/ssl_request_log`))
191
+ fs.copySync(`/opt/lampp/logs/ssl_request_log`, `/opt/lampp/logs/ssl_request.log`);
192
+
193
+ // Initialize the service after installation
194
+ await this.initService({ daemon: true });
107
195
  }
108
- },
109
- createApp: async ({ port, host, path, directory, rootHostPath, redirect, redirectTarget, resetRouter }) => {
110
- if (!Lampp.enabled()) return { disabled: true };
111
- if (!Lampp.ports.includes(port)) Lampp.ports.push(port);
112
- if (resetRouter) Lampp.removeRouter();
113
- Lampp.appendRouter(`
196
+ }
197
+
198
+ /**
199
+ * Creates and appends a new Apache VirtualHost entry to the router configuration for a web application.
200
+ * The router is then applied by calling {@link LamppService#initService}.
201
+ *
202
+ * @memberof LamppService.prototype
203
+ * @param {object} options - Configuration options for the new web application.
204
+ * @param {number} options.port - The port the VirtualHost should listen on.
205
+ * @param {string} options.host - The ServerName/host for the VirtualHost.
206
+ * @param {string} options.path - The base path for error documents (e.g., '/app').
207
+ * @param {string} [options.directory] - Optional absolute path to the document root.
208
+ * @param {string} [options.rootHostPath] - Relative path from the root directory to the document root, used if `directory` is not provided.
209
+ * @param {boolean} [options.redirect] - If true, enables RewriteEngine for redirection.
210
+ * @param {string} [options.redirectTarget] - The target URL for redirection.
211
+ * @param {boolean} [options.resetRouter] - If true, clears the existing router configuration before appending the new one.
212
+ * @returns {{disabled: boolean}} An object indicating if the service is disabled.
213
+ */
214
+ createApp({ port, host, path, directory, rootHostPath, redirect, redirectTarget, resetRouter }) {
215
+ if (!this.enabled()) return { disabled: true };
216
+
217
+ if (!this.ports.includes(port)) this.ports.push(port);
218
+ if (resetRouter) this.removeRouter();
219
+
220
+ const documentRoot = directory ? directory : `${getRootDirectory()}${rootHostPath}`;
221
+
222
+ // Append the new VirtualHost configuration
223
+ this.appendRouter(`
114
224
  Listen ${port}
115
225
 
116
226
  <VirtualHost *:${port}>
117
- DocumentRoot "${directory ? directory : `${getRootDirectory()}${rootHostPath}`}"
227
+ DocumentRoot "${documentRoot}"
118
228
  ServerName ${host}:${port}
119
229
 
120
- <Directory "${directory ? directory : `${getRootDirectory()}${rootHostPath}`}">
230
+ <Directory "${documentRoot}">
121
231
  Options Indexes FollowSymLinks MultiViews
122
232
  AllowOverride All
123
233
  Require all granted
@@ -128,12 +238,14 @@ Listen ${port}
128
238
  ? `
129
239
  RewriteEngine on
130
240
 
241
+ # Exclude the ACME challenge path for certificate renewals
131
242
  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge
132
243
  RewriteRule ^(.*)$ ${redirectTarget}%{REQUEST_URI} [R=302,L]
133
244
  `
134
245
  : ''
135
246
  }
136
247
 
248
+ # Custom Error Documents
137
249
  ErrorDocument 400 ${path === '/' ? '' : path}/400.html
138
250
  ErrorDocument 404 ${path === '/' ? '' : path}/400.html
139
251
  ErrorDocument 500 ${path === '/' ? '' : path}/500.html
@@ -144,56 +256,69 @@ Listen ${port}
144
256
  </VirtualHost>
145
257
 
146
258
  `);
147
- // ERR too many redirects:
148
- // Check: SELECT * FROM database.wp_options where option_name = 'siteurl' or option_name = 'home';
149
- // Check: wp-config.php
150
- // if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
151
- // $_SERVER['HTTPS'] = 'on';
152
- // }
153
- // For plugins:
154
- // define( 'FS_METHOD', 'direct' );
155
-
156
- // ErrorDocument 404 /custom_404.html
157
- // ErrorDocument 500 /custom_50x.html
158
- // ErrorDocument 502 /custom_50x.html
159
- // ErrorDocument 503 /custom_50x.html
160
- // ErrorDocument 504 /custom_50x.html
161
-
162
- // Respond When Error Pages are Directly Requested
163
-
164
- // <Files "custom_404.html">
165
- // <If "-z %{ENV:REDIRECT_STATUS}">
166
- // RedirectMatch 404 ^/custom_404.html$
167
- // </If>
168
- // </Files>
169
-
170
- // <Files "custom_50x.html">
171
- // <If "-z %{ENV:REDIRECT_STATUS}">
172
- // RedirectMatch 404 ^/custom_50x.html$
173
- // </If>
174
- // </Files>
175
-
176
- // Add www or https with htaccess rewrite
177
-
178
- // Options +FollowSymLinks
179
- // RewriteEngine On
180
- // RewriteCond %{HTTP_HOST} ^ejemplo.com [NC]
181
- // RewriteRule ^(.*)$ http://ejemplo.com/$1 [R=301,L]
182
-
183
- // Redirect http to https with htaccess rewrite
184
-
185
- // RewriteEngine On
186
- // RewriteCond %{SERVER_PORT} 80
187
- // RewriteRule ^(.*)$ https://www.ejemplo.com/$1 [R,L]
188
-
189
- // Redirect to HTTPS with www subdomain
190
-
191
- // RewriteEngine On
192
- // RewriteCond %{HTTPS} off [OR]
193
- // RewriteCond %{HTTP_HOST} ^www\. [NC]
194
- // RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
195
- // RewriteRule ^ https://%1%{REQUEST_URI} [L,NE,R=301]
196
- },
197
- };
259
+
260
+ return { disabled: false };
261
+ }
262
+ }
263
+
264
+ /**
265
+ * @namespace LamppService
266
+ * @description Exported singleton instance of the LamppService class.
267
+ * This object is used to interact with the Lampp configuration and service.
268
+ * @type {LamppService}
269
+ */
270
+ const Lampp = new LamppService();
271
+
272
+ // -- helper info --
273
+
274
+ // ERR too many redirects:
275
+ // Check: SELECT * FROM database.wp_options where option_name = 'siteurl' or option_name = 'home';
276
+ // Check: wp-config.php
277
+ // if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
278
+ // $_SERVER['HTTPS'] = 'on';
279
+ // }
280
+ // For plugins:
281
+ // define( 'FS_METHOD', 'direct' );
282
+
283
+ // ErrorDocument 404 /custom_404.html
284
+ // ErrorDocument 500 /custom_50x.html
285
+ // ErrorDocument 502 /custom_50x.html
286
+ // ErrorDocument 503 /custom_50x.html
287
+ // ErrorDocument 504 /custom_50x.html
288
+
289
+ // Respond When Error Pages are Directly Requested
290
+
291
+ // <Files "custom_404.html">
292
+ // <If "-z %{ENV:REDIRECT_STATUS}">
293
+ // RedirectMatch 404 ^/custom_404.html$
294
+ // </If>
295
+ // </Files>
296
+
297
+ // <Files "custom_50x.html">
298
+ // <If "-z %{ENV:REDIRECT_STATUS}">
299
+ // RedirectMatch 404 ^/custom_50x.html$
300
+ // </If>
301
+ // </Files>
302
+
303
+ // Add www or https with htaccess rewrite
304
+
305
+ // Options +FollowSymLinks
306
+ // RewriteEngine On
307
+ // RewriteCond %{HTTP_HOST} ^example.com [NC]
308
+ // RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
309
+
310
+ // Redirect http to https with htaccess rewrite
311
+
312
+ // RewriteEngine On
313
+ // RewriteCond %{SERVER_PORT} 80
314
+ // RewriteRule ^(.*)$ https://www.example.com/$1 [R,L]
315
+
316
+ // Redirect to HTTPS with www subdomain
317
+
318
+ // RewriteEngine On
319
+ // RewriteCond %{HTTPS} off [OR]
320
+ // RewriteCond %{HTTP_HOST} ^www\. [NC]
321
+ // RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
322
+ // RewriteRule ^ https://%1%{REQUEST_URI} [L,NE,R=301]
198
323
 
199
324
  export { Lampp };
@@ -306,6 +306,61 @@ const validatePasswordMiddleware = (req) => {
306
306
  };
307
307
 
308
308
  // ---------- Session & Refresh token management ----------
309
+
310
+ /**
311
+ * Creates cookie options for the refresh token.
312
+ * @param {import('express').Request} req The Express request object.
313
+ * @returns {object} Cookie options.
314
+ * @memberof Auth
315
+ */
316
+ const cookieOptionsFactory = (req) => {
317
+ const isProduction = process.env.NODE_ENV === 'production';
318
+
319
+ // Determine hostname safely:
320
+ // Prefer origin header if present (it contains protocol + host)
321
+ let candidateHost = undefined;
322
+ try {
323
+ if (req.headers && req.headers.origin) {
324
+ candidateHost = new URL(req.headers.origin).hostname;
325
+ }
326
+ } catch (e) {
327
+ /* ignore parse error */
328
+ logger.error(e);
329
+ }
330
+
331
+ // fallback to req.hostname (Express sets this; ensure trust proxy if behind proxy)
332
+ if (!candidateHost) candidateHost = (req.hostname || '').split(':')[0];
333
+
334
+ candidateHost = (candidateHost || '').trim().replace(/^www\./i, '');
335
+
336
+ // Do not set domain for localhost, 127.x.x.x, or plain IPs
337
+ const isIpOrLocal = /^(localhost|127(?:\.\d+){0,2}\.\d+|\[::1\]|\d+\.\d+\.\d+\.\d+)$/i.test(candidateHost);
338
+ const domain = isProduction && candidateHost && !isIpOrLocal ? `.${candidateHost}` : undefined;
339
+
340
+ // Determine if request is secure: respect X-Forwarded-Proto when behind proxy
341
+ const forwardedProto = (req.headers && req.headers['x-forwarded-proto']) || '';
342
+ const reqIsSecure = Boolean(req.secure || forwardedProto.split(',')[0] === 'https');
343
+
344
+ // secure must be true for SameSite=None to work across sites
345
+ const secure = isProduction ? reqIsSecure : false;
346
+ const sameSite = secure ? 'None' : 'Lax';
347
+
348
+ // Safe parse of maxAge minutes
349
+ const minutes = Number.parseInt(process.env.REFRESH_EXPIRE_MINUTES, 10);
350
+ const maxAge = Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : undefined;
351
+
352
+ const opts = {
353
+ httpOnly: true,
354
+ secure,
355
+ sameSite,
356
+ path: '/',
357
+ };
358
+ if (typeof maxAge !== 'undefined') opts.maxAge = maxAge;
359
+ if (domain) opts.domain = domain;
360
+
361
+ return opts;
362
+ };
363
+
309
364
  /**
310
365
  * Create session and set refresh cookie. Rotating and hashed stored token.
311
366
  * @param {object} user The user object.
@@ -339,13 +394,7 @@ async function createSessionAndUserToken(user, User, req, res, options = { host:
339
394
  const jwtid = session._id.toString();
340
395
 
341
396
  // Secure cookie settings
342
- res.cookie('refreshToken', refreshToken, {
343
- httpOnly: true,
344
- secure: process.env.NODE_ENV === 'production',
345
- sameSite: 'Lax',
346
- maxAge: parseInt(process.env.REFRESH_EXPIRE_MINUTES) * 60 * 1000,
347
- path: '/',
348
- });
397
+ res.cookie('refreshToken', refreshToken, cookieOptionsFactory(req));
349
398
 
350
399
  return { jwtid };
351
400
  }
@@ -439,13 +488,7 @@ async function refreshSessionAndToken(req, res, User, options = { host: '', path
439
488
 
440
489
  logger.warn('Refreshed session for user ' + user.email);
441
490
 
442
- res.cookie('refreshToken', refreshToken, {
443
- httpOnly: true,
444
- secure: process.env.NODE_ENV === 'production',
445
- sameSite: 'Lax',
446
- maxAge: parseInt(process.env.REFRESH_EXPIRE_MINUTES) * 60 * 1000,
447
- path: '/',
448
- });
491
+ res.cookie('refreshToken', refreshToken, cookieOptionsFactory(req));
449
492
 
450
493
  return jwtSign(
451
494
  UserDto.auth.payload(user, session._id.toString(), req.ip, req.headers['user-agent'], options.host, options.path),
@@ -533,14 +576,22 @@ function applySecurity(app, opts = {}) {
533
576
  blockAllMixedContent: [],
534
577
  fontSrc: ["'self'", httpDirective, 'data:'],
535
578
  frameAncestors: frameAncestors,
536
- imgSrc: ["'self'", 'data:', httpDirective],
579
+ imgSrc: ["'self'", 'data:', httpDirective, 'https:', 'blob:'],
537
580
  objectSrc: ["'none'"],
538
581
  // script-src and script-src-elem include dynamic nonce
539
- scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
582
+ scriptSrc: [
583
+ "'self'",
584
+ (req, res) => `'nonce-${res.locals.nonce}'`,
585
+ (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : ''),
586
+ ],
540
587
  scriptSrcElem: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
541
588
  // style-src: avoid 'unsafe-inline' when possible; if you must inline styles,
542
589
  // use a nonce for them too (or hash).
543
- styleSrc: ["'self'", httpDirective, (req, res) => `'nonce-${res.locals.nonce}'`],
590
+ styleSrc: [
591
+ "'self'",
592
+ httpDirective,
593
+ (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : `'nonce-${res.locals.nonce}'`),
594
+ ],
544
595
  // deny plugins
545
596
  objectSrc: ["'none'"],
546
597
  },