@botfabrik/engine-webclient 4.109.12-alpha.2 → 4.109.12-alpha.4
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/dist/applySecurityMiddleware.d.ts +10 -0
- package/dist/applySecurityMiddleware.js +116 -0
- package/dist/applySecurityMiddleware.test.d.ts +1 -0
- package/dist/applySecurityMiddleware.test.js +99 -0
- package/dist/client/assets/{index-DfW2mfd6.js → index-DHHRN8Yr.js} +39 -39
- package/dist/client/assets/{index-DfW2mfd6.js.map → index-DHHRN8Yr.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/index.js +24 -97
- package/package.json +9 -7
package/dist/client/index.html
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<meta name="theme-color" content="#000000" />
|
|
14
14
|
<meta name="google" content="notranslate" />
|
|
15
15
|
<title>Bubble Chat Client</title>
|
|
16
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
+
<script type="module" crossorigin src="./assets/index-DHHRN8Yr.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="./assets/index-BKtBdibb.css">
|
|
18
18
|
</head>
|
|
19
19
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Actions, ActionTypes, BotUser, TextMessage, } from '@botfabrik/engine-domain';
|
|
2
2
|
import { getPdf } from '@botfabrik/engine-transcript-export';
|
|
3
|
-
import cors from 'cors';
|
|
4
3
|
import { Router, static as serveStatic, } from 'express';
|
|
5
|
-
import helmet from 'helmet';
|
|
6
4
|
import { dirname } from 'node:path';
|
|
7
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { Server as SocketIOServer } from 'socket.io';
|
|
7
|
+
import { applySecurityMiddleware } from './applySecurityMiddleware.js';
|
|
8
8
|
import { setUpSamlAuth, storeLoginRequestToken, verifyLoginToken, } from './auth/index.js';
|
|
9
9
|
import { CLIENT_TYPE } from './constants.js';
|
|
10
10
|
import createSessionInfo from './createSessionInfo.js';
|
|
@@ -24,100 +24,7 @@ const __dirname = dirname(__filename);
|
|
|
24
24
|
export default (clientName, environment, props) => async (bot) => {
|
|
25
25
|
const logger = bot.logger.child({ clientType: CLIENT_TYPE, clientName });
|
|
26
26
|
const router = Router();
|
|
27
|
-
|
|
28
|
-
// When allowedOrigins is undefined, all origins are permitted (backward-compatible default).
|
|
29
|
-
// When it is set (even to an empty array), only the listed origins are allowed.
|
|
30
|
-
const allowedOrigins = props.allowedOrigins;
|
|
31
|
-
// When trustedResourceOrigins is undefined, the webclient may load resources from all origins.
|
|
32
|
-
// When it is set, only those origins are trusted as resource sources (font-src, img-src, …).
|
|
33
|
-
const trustedResourceOrigins = props.trustedResourceOrigins;
|
|
34
|
-
router.use(cors({
|
|
35
|
-
// Access-Control-Allow-Origin
|
|
36
|
-
origin: allowedOrigins === undefined
|
|
37
|
-
? true // all origins allowed
|
|
38
|
-
: (origin, callback) => {
|
|
39
|
-
if (!origin || allowedOrigins.includes(origin)) {
|
|
40
|
-
callback(null, true);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
callback(new Error(`CORS: Origin '${origin}' is not allowed`));
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
// Access-Control-Allow-Methods
|
|
47
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
48
|
-
// Access-Control-Allow-Headers
|
|
49
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
50
|
-
credentials: true,
|
|
51
|
-
}));
|
|
52
|
-
// Security: Helmet (CSP + security headers)
|
|
53
|
-
router.use(helmet({
|
|
54
|
-
contentSecurityPolicy: {
|
|
55
|
-
directives: {
|
|
56
|
-
defaultSrc: ["'self'"],
|
|
57
|
-
scriptSrc: ["'self'"],
|
|
58
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
59
|
-
// images: restrict to trusted resource origins when defined, otherwise allow all HTTPS
|
|
60
|
-
imgSrc: trustedResourceOrigins === undefined
|
|
61
|
-
? ["'self'", 'data:', 'https:']
|
|
62
|
-
: ["'self'", 'data:', ...trustedResourceOrigins],
|
|
63
|
-
connectSrc: [
|
|
64
|
-
"'self'",
|
|
65
|
-
// allow websocket connections back to the same server
|
|
66
|
-
bot.webserver.baseUrl.replace(/^http/, 'ws'),
|
|
67
|
-
bot.webserver.baseUrl.replace(/^http/, 'wss'),
|
|
68
|
-
],
|
|
69
|
-
// fonts may be loaded from trusted resource origins (e.g. Google Fonts);
|
|
70
|
-
// when no trustedResourceOrigins are configured, allow fonts from all origins
|
|
71
|
-
fontSrc: trustedResourceOrigins === undefined
|
|
72
|
-
? ['*']
|
|
73
|
-
: ["'self'", ...trustedResourceOrigins],
|
|
74
|
-
// when no allowedOrigins are configured, allow framing from all origins
|
|
75
|
-
frameAncestors: allowedOrigins === undefined
|
|
76
|
-
? ['*']
|
|
77
|
-
: ["'self'", ...allowedOrigins],
|
|
78
|
-
// frame-src controls what this page may embed in an iframe.
|
|
79
|
-
// The /embed page loads the chatbot in an iframe on the same server ('self').
|
|
80
|
-
// When allowedOrigins are set, those must also be allowed (e.g. standalone view).
|
|
81
|
-
frameSrc: ["'self'", ...(allowedOrigins ?? [])],
|
|
82
|
-
objectSrc: ["'none'"], // disable Flash/plugins
|
|
83
|
-
baseUri: ["'self'"], // prevent <base>-tag injection
|
|
84
|
-
// When SAML auth is configured, the browser POSTs a form to the external IdP.
|
|
85
|
-
// The IdP URL must therefore be explicitly allowed alongside 'self'.
|
|
86
|
-
formAction: props.auth?.saml.entryPoint
|
|
87
|
-
? ["'self'", props.auth.saml.entryPoint]
|
|
88
|
-
: ["'self'"],
|
|
89
|
-
// upgrade-insecure-requests is intentionally omitted:
|
|
90
|
-
// the webclient is embedded via iframe on external pages which may themselves be HTTP,
|
|
91
|
-
// and the directive would also affect those sub-resource loads in some browsers.
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
// Disable X-Frame-Options: we control framing exclusively via CSP frame-ancestors above.
|
|
95
|
-
// X-Frame-Options: SAMEORIGIN (helmet default) would block cross-origin iframe embedding.
|
|
96
|
-
frameguard: false,
|
|
97
|
-
// Send origin only, without path – sufficient for analytics while avoiding leaking URLs
|
|
98
|
-
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
99
|
-
// Disable HSTS: HTTPS enforcement is already handled globally in engine-core.
|
|
100
|
-
// Enabling it here would cause the browser to enforce HTTPS for the entire origin,
|
|
101
|
-
// which breaks local development (http://localhost:3000).
|
|
102
|
-
hsts: false,
|
|
103
|
-
// resources served by the webclient must be loadable cross-origin (e.g. embed script)
|
|
104
|
-
crossOriginResourcePolicy: { policy: 'cross-origin' },
|
|
105
|
-
}));
|
|
106
|
-
// Permissions-Policy: restrict browser feature access to the minimum required.
|
|
107
|
-
// Microphone is only permitted when speech-to-text is configured.
|
|
108
|
-
router.use((_req, res, next) => {
|
|
109
|
-
const microphonePolicy = props.speech
|
|
110
|
-
? 'microphone=(self)'
|
|
111
|
-
: 'microphone=()';
|
|
112
|
-
res.setHeader('Permissions-Policy', [
|
|
113
|
-
microphonePolicy,
|
|
114
|
-
'camera=()',
|
|
115
|
-
'geolocation=()',
|
|
116
|
-
'payment=()',
|
|
117
|
-
'usb=()',
|
|
118
|
-
].join(', '));
|
|
119
|
-
next();
|
|
120
|
-
});
|
|
27
|
+
applySecurityMiddleware(router, props, bot.webserver.baseUrl);
|
|
121
28
|
// serve transcript pdf
|
|
122
29
|
router.use('/transcript-pdf/:sessionId', async (req, res) => {
|
|
123
30
|
const sessionId = req.params['sessionId'];
|
|
@@ -184,7 +91,26 @@ export default (clientName, environment, props) => async (bot) => {
|
|
|
184
91
|
// mount router on the main app
|
|
185
92
|
bot.webserver.express.use(`/${clientName}`, router);
|
|
186
93
|
logger.info(`Webclient will be available on route: /${clientName}`);
|
|
187
|
-
|
|
94
|
+
// Each webclient gets its own Socket.IO server with a dedicated transport
|
|
95
|
+
// path: /website-messenger/{clientName}/socket.io
|
|
96
|
+
//
|
|
97
|
+
// destroyUpgrade: false is required when multiple SocketIOServer instances
|
|
98
|
+
// share the same httpServer. By default engine.io destroys any WebSocket
|
|
99
|
+
// upgrade whose URL doesn't match after 1 second – with multiple instances
|
|
100
|
+
// that would kill the connections of all other instances.
|
|
101
|
+
// With destroyUpgrade: false each instance simply ignores upgrades that
|
|
102
|
+
// don't belong to it, and the matching instance handles them.
|
|
103
|
+
//
|
|
104
|
+
// No CORS configuration needed: the Socket.IO connection is always initiated
|
|
105
|
+
// from the chatbot page, which is served from the same origin as the server.
|
|
106
|
+
const io = new SocketIOServer(bot.webserver.httpServer, {
|
|
107
|
+
path: `/website-messenger/${clientName}/socket.io`,
|
|
108
|
+
destroyUpgrade: false,
|
|
109
|
+
...(isLocalhost(bot.webserver.baseUrl)
|
|
110
|
+
? { cors: { origin: 'http://localhost:3001' } } // allow CORS for local development
|
|
111
|
+
: {}),
|
|
112
|
+
});
|
|
113
|
+
const nsp = io.of('/');
|
|
188
114
|
nsp.on('connection', async (socket) => {
|
|
189
115
|
try {
|
|
190
116
|
sendConfigurationToClient(socket, props, bot, clientName);
|
|
@@ -332,3 +258,4 @@ const sendConfigurationToClient = (socket, props, bot, clientName) => {
|
|
|
332
258
|
});
|
|
333
259
|
}
|
|
334
260
|
};
|
|
261
|
+
const isLocalhost = (baseUrl) => baseUrl === 'http://localhost:3000';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botfabrik/engine-webclient",
|
|
3
|
-
"version": "4.109.12-alpha.
|
|
3
|
+
"version": "4.109.12-alpha.4",
|
|
4
4
|
"description": "Webclient for Botfabriks Bot Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dev:embed": "tsx --watch ./run-embed-local.ts"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@botfabrik/engine-domain": "^4.109.
|
|
35
|
-
"@botfabrik/engine-transcript-export": "^4.109.
|
|
36
|
-
"@botfabrik/engine-utils": "^4.109.
|
|
34
|
+
"@botfabrik/engine-domain": "^4.109.12-alpha.4",
|
|
35
|
+
"@botfabrik/engine-transcript-export": "^4.109.12-alpha.4",
|
|
36
|
+
"@botfabrik/engine-utils": "^4.109.12-alpha.4",
|
|
37
37
|
"@google-cloud/speech": "^7.3.0",
|
|
38
38
|
"@node-saml/passport-saml": "^5.1.0",
|
|
39
39
|
"@types/cors": "^2.8.19",
|
|
@@ -42,16 +42,18 @@
|
|
|
42
42
|
"flat": "^6.0.1",
|
|
43
43
|
"helmet": "^8.1.0",
|
|
44
44
|
"jose": "^6.1.3",
|
|
45
|
-
"passport": "^0.7.0"
|
|
45
|
+
"passport": "^0.7.0",
|
|
46
|
+
"socket.io": "^4.8.3"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/express": "^5.0.6",
|
|
49
50
|
"@types/jsonwebtoken": "^9.0.10",
|
|
50
51
|
"@types/serve-static": "^2.2.0",
|
|
52
|
+
"@types/supertest": "^7.2.0",
|
|
51
53
|
"@types/ua-parser-js": "^0.7.39",
|
|
52
|
-
"
|
|
54
|
+
"supertest": "^7.2.2",
|
|
53
55
|
"tsx": "^4.21.0",
|
|
54
56
|
"typescript": "5.9.3"
|
|
55
57
|
},
|
|
56
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "0823e487c73ce26568e77cf598e83866f469360a"
|
|
57
59
|
}
|