api-ape 3.0.2 → 4.1.0
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 +59 -572
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -203
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +31 -6
- package/server/README.md +272 -67
- package/server/adapters/README.md +23 -14
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +322 -71
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -219
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -224
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -311
- package/server/lib/broadcast.js +0 -146
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Authorization Middleware for api-ape Server
|
|
3
|
+
*
|
|
4
|
+
* Provides authorization checks for incoming messages based on
|
|
5
|
+
* authentication tier and permissions.
|
|
6
|
+
*
|
|
7
|
+
* ## Usage
|
|
8
|
+
*
|
|
9
|
+
* The middleware can be configured with endpoint-specific requirements:
|
|
10
|
+
*
|
|
11
|
+
* ```javascript
|
|
12
|
+
* const authz = createAuthMiddleware({
|
|
13
|
+
* requirements: {
|
|
14
|
+
* 'admin/users': { tier: 2, permissions: ['admin:users'] },
|
|
15
|
+
* 'chat/send': { tier: 1, permissions: ['chat:send'] },
|
|
16
|
+
* 'public/status': { tier: 0 } // guest allowed
|
|
17
|
+
* },
|
|
18
|
+
* defaultTier: 0 // default for unlisted endpoints
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module server/socket/authMiddleware
|
|
23
|
+
* @see {@link module:server/security/auth} for authentication framework
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const { AuthTier } = require("../security/auth");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Authorization error response
|
|
30
|
+
* @typedef {Object} AuthzError
|
|
31
|
+
* @property {string} type - Always "authz_fail"
|
|
32
|
+
* @property {string} reason - Error reason code
|
|
33
|
+
* @property {string} [required] - What was required
|
|
34
|
+
* @property {number} [requiredTier] - Required tier
|
|
35
|
+
* @property {number} [currentTier] - Current tier
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Endpoint authorization requirement
|
|
40
|
+
* @typedef {Object} EndpointRequirement
|
|
41
|
+
* @property {number} [tier=0] - Minimum required tier
|
|
42
|
+
* @property {string[]} [permissions=[]] - Required permissions (any)
|
|
43
|
+
* @property {string[]} [roles=[]] - Required roles (any)
|
|
44
|
+
* @property {boolean} [requireAll=false] - Require all permissions/roles (not just any)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Authorization middleware configuration
|
|
49
|
+
* @typedef {Object} AuthMiddlewareConfig
|
|
50
|
+
* @property {Object<string, EndpointRequirement>} [requirements={}] - Per-endpoint requirements
|
|
51
|
+
* @property {number} [defaultTier=0] - Default tier for unlisted endpoints
|
|
52
|
+
* @property {boolean} [requireAuthByDefault=false] - Require auth for unlisted endpoints
|
|
53
|
+
* @property {Function} [onAuthzFail] - Callback on authorization failure
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create authorization middleware
|
|
58
|
+
*
|
|
59
|
+
* @param {AuthMiddlewareConfig} [config={}] - Configuration options
|
|
60
|
+
* @returns {Object} Authorization middleware
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const authz = createAuthMiddleware({
|
|
64
|
+
* requirements: {
|
|
65
|
+
* 'admin/*': { tier: 2 },
|
|
66
|
+
* 'user/profile': { tier: 1 },
|
|
67
|
+
* 'public/*': { tier: 0 }
|
|
68
|
+
* },
|
|
69
|
+
* defaultTier: 1
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Check authorization:
|
|
73
|
+
* const result = authz.check(socketAuth, 'admin/users');
|
|
74
|
+
* if (!result.allowed) {
|
|
75
|
+
* send(queryId, 'authz_fail', result, null);
|
|
76
|
+
* return;
|
|
77
|
+
* }
|
|
78
|
+
*/
|
|
79
|
+
function createAuthMiddleware(config = {}) {
|
|
80
|
+
const {
|
|
81
|
+
requirements = {},
|
|
82
|
+
defaultTier = 0,
|
|
83
|
+
requireAuthByDefault = false,
|
|
84
|
+
onAuthzFail = () => {},
|
|
85
|
+
} = config;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find requirement for an endpoint, supporting wildcards
|
|
89
|
+
*
|
|
90
|
+
* @param {string} endpoint - Endpoint path
|
|
91
|
+
* @returns {EndpointRequirement|null} Requirement or null
|
|
92
|
+
*/
|
|
93
|
+
function findRequirement(endpoint) {
|
|
94
|
+
if (requirements[endpoint]) {
|
|
95
|
+
return requirements[endpoint];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const parts = endpoint.split("/");
|
|
99
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
100
|
+
const wildcardPath = parts.slice(0, i).join("/") + "/*";
|
|
101
|
+
if (requirements[wildcardPath]) {
|
|
102
|
+
return requirements[wildcardPath];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (requirements["*"]) {
|
|
107
|
+
return requirements["*"];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if principal has required permission
|
|
115
|
+
*
|
|
116
|
+
* @param {Object} principal - Authenticated principal
|
|
117
|
+
* @param {string} permission - Required permission
|
|
118
|
+
* @returns {boolean} Whether principal has permission
|
|
119
|
+
*/
|
|
120
|
+
function hasPermission(principal, permission) {
|
|
121
|
+
if (!principal || !principal.permissions) return false;
|
|
122
|
+
|
|
123
|
+
if (principal.permissions[permission] === true) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parts = permission.split(":");
|
|
128
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
129
|
+
const wildcardPerm = parts.slice(0, i).join(":") + ":*";
|
|
130
|
+
if (principal.permissions[wildcardPerm] === true) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (principal.permissions["*"] === true) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if principal has required role
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} principal - Authenticated principal
|
|
146
|
+
* @param {string} role - Required role
|
|
147
|
+
* @returns {boolean} Whether principal has role
|
|
148
|
+
*/
|
|
149
|
+
function hasRole(principal, role) {
|
|
150
|
+
if (!principal || !principal.roles) return false;
|
|
151
|
+
return principal.roles.includes(role) || principal.roles.includes("*");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check authorization for an endpoint
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} socketAuth - Socket auth manager
|
|
158
|
+
* @param {string} endpoint - Endpoint being accessed
|
|
159
|
+
* @param {Object} [context={}] - Additional context
|
|
160
|
+
* @returns {Object} Authorization result { allowed, reason, ... }
|
|
161
|
+
*/
|
|
162
|
+
function check(socketAuth, endpoint, context = {}) {
|
|
163
|
+
const state = socketAuth.getState();
|
|
164
|
+
const requirement = findRequirement(endpoint);
|
|
165
|
+
|
|
166
|
+
let requiredTier = defaultTier;
|
|
167
|
+
let requiredPermissions = [];
|
|
168
|
+
let requiredRoles = [];
|
|
169
|
+
let requireAll = false;
|
|
170
|
+
|
|
171
|
+
if (requirement) {
|
|
172
|
+
requiredTier = requirement.tier ?? defaultTier;
|
|
173
|
+
requiredPermissions = requirement.permissions || [];
|
|
174
|
+
requiredRoles = requirement.roles || [];
|
|
175
|
+
requireAll = requirement.requireAll || false;
|
|
176
|
+
} else if (requireAuthByDefault) {
|
|
177
|
+
requiredTier = AuthTier.BASIC;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (state.tier < requiredTier) {
|
|
181
|
+
const result = {
|
|
182
|
+
allowed: false,
|
|
183
|
+
reason: "INSUFFICIENT_TIER",
|
|
184
|
+
requiredTier,
|
|
185
|
+
currentTier: state.tier,
|
|
186
|
+
endpoint,
|
|
187
|
+
};
|
|
188
|
+
onAuthzFail(endpoint, result, context);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (requiredPermissions.length > 0) {
|
|
193
|
+
const hasRequired = requireAll
|
|
194
|
+
? requiredPermissions.every((p) => hasPermission(state.principal, p))
|
|
195
|
+
: requiredPermissions.some((p) => hasPermission(state.principal, p));
|
|
196
|
+
|
|
197
|
+
if (!hasRequired) {
|
|
198
|
+
const result = {
|
|
199
|
+
allowed: false,
|
|
200
|
+
reason: "MISSING_PERMISSION",
|
|
201
|
+
required: requiredPermissions,
|
|
202
|
+
endpoint,
|
|
203
|
+
};
|
|
204
|
+
onAuthzFail(endpoint, result, context);
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (requiredRoles.length > 0) {
|
|
210
|
+
const hasRequired = requireAll
|
|
211
|
+
? requiredRoles.every((r) => hasRole(state.principal, r))
|
|
212
|
+
: requiredRoles.some((r) => hasRole(state.principal, r));
|
|
213
|
+
|
|
214
|
+
if (!hasRequired) {
|
|
215
|
+
const result = {
|
|
216
|
+
allowed: false,
|
|
217
|
+
reason: "MISSING_ROLE",
|
|
218
|
+
required: requiredRoles,
|
|
219
|
+
endpoint,
|
|
220
|
+
};
|
|
221
|
+
onAuthzFail(endpoint, result, context);
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
allowed: true,
|
|
228
|
+
tier: state.tier,
|
|
229
|
+
principal: state.principal,
|
|
230
|
+
endpoint,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Create an authz_fail response
|
|
236
|
+
*
|
|
237
|
+
* @param {Object} checkResult - Result from check()
|
|
238
|
+
* @returns {Object} Response suitable for sending to client
|
|
239
|
+
*/
|
|
240
|
+
function createFailResponse(checkResult) {
|
|
241
|
+
return {
|
|
242
|
+
type: "authz_fail",
|
|
243
|
+
reason: checkResult.reason,
|
|
244
|
+
required: checkResult.required || checkResult.requiredTier,
|
|
245
|
+
currentTier: checkResult.currentTier,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Set requirement for an endpoint
|
|
251
|
+
*
|
|
252
|
+
* @param {string} endpoint - Endpoint path
|
|
253
|
+
* @param {EndpointRequirement} requirement - Requirement config
|
|
254
|
+
*/
|
|
255
|
+
function setRequirement(endpoint, requirement) {
|
|
256
|
+
requirements[endpoint] = requirement;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Remove requirement for an endpoint
|
|
261
|
+
*
|
|
262
|
+
* @param {string} endpoint - Endpoint path
|
|
263
|
+
*/
|
|
264
|
+
function removeRequirement(endpoint) {
|
|
265
|
+
delete requirements[endpoint];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get all configured requirements
|
|
270
|
+
* @returns {Object<string, EndpointRequirement>} Requirements map
|
|
271
|
+
*/
|
|
272
|
+
function getRequirements() {
|
|
273
|
+
return { ...requirements };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
check,
|
|
278
|
+
createFailResponse,
|
|
279
|
+
setRequirement,
|
|
280
|
+
removeRequirement,
|
|
281
|
+
getRequirements,
|
|
282
|
+
findRequirement,
|
|
283
|
+
hasPermission,
|
|
284
|
+
hasRole,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Default authorization middleware instance
|
|
290
|
+
*
|
|
291
|
+
* Created with default settings (tier 0 required for all endpoints).
|
|
292
|
+
* Can be replaced or configured in the main server setup.
|
|
293
|
+
*/
|
|
294
|
+
const defaultAuthMiddleware = createAuthMiddleware();
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
createAuthMiddleware,
|
|
298
|
+
defaultAuthMiddleware,
|
|
299
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Socket Module Files
|
|
2
|
+
|
|
3
|
+
This module handles WebSocket message processing for api-ape servers. It manages the complete lifecycle of messages flowing between clients and controllers—from initial connection validation through message parsing, controller invocation, and response serialization.
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
|
|
7
|
+
- **JSS encoding** — Always use `utils/jss` for message parsing/serialization; never raw `JSON.parse/stringify`
|
|
8
|
+
- **QueryId correlation** — Every response must include the original `queryId` for client-side Promise resolution
|
|
9
|
+
- **Binary tag system** — Use `tagUtils.js` for all binary data detection; don't implement custom tag parsing
|
|
10
|
+
- **Controller context** — Controllers receive `this` with `clientId`, `broadcast`, `send`, and embedded data
|
|
11
|
+
- **Error handling** — Catch all controller errors and send error responses; never let exceptions crash the connection
|
|
12
|
+
- **Security first** — All connections must pass `open.js` validation before processing messages
|
|
13
|
+
|
|
14
|
+
## Directory Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
socket/
|
|
18
|
+
├── authMiddleware.js # Authorization middleware for endpoint access control
|
|
19
|
+
├── open.js # Connection validation & security check
|
|
20
|
+
├── receive.js # Incoming message handler
|
|
21
|
+
├── receiveContext.js # Controller context factory
|
|
22
|
+
├── send.js # Outgoing message handler
|
|
23
|
+
└── tagUtils.js # Binary data tag parsing utilities
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Files
|
|
27
|
+
|
|
28
|
+
### `authMiddleware.js`
|
|
29
|
+
|
|
30
|
+
Authorization middleware for endpoint access control:
|
|
31
|
+
|
|
32
|
+
- Checks auth tier requirements before controller invocation
|
|
33
|
+
- Supports wildcard endpoint patterns (`admin/*`, `*`)
|
|
34
|
+
- Permission and role-based authorization with `requireAll` option
|
|
35
|
+
- Configurable per-endpoint requirements via `setRequirement()`
|
|
36
|
+
- Creates standard `authz_fail` responses for unauthorized requests
|
|
37
|
+
|
|
38
|
+
### `open.js`
|
|
39
|
+
|
|
40
|
+
Connection open handler called when a new WebSocket connection is established:
|
|
41
|
+
|
|
42
|
+
- Validates the connection origin against the host (CSRF protection)
|
|
43
|
+
- Delegates to `security/origin.js` for origin verification
|
|
44
|
+
- Returns `true` if connection is allowed, `false` to reject
|
|
45
|
+
|
|
46
|
+
### `receive.js`
|
|
47
|
+
|
|
48
|
+
Incoming message handler that processes client requests:
|
|
49
|
+
|
|
50
|
+
- Parses incoming WebSocket messages (JSS encoded JSON)
|
|
51
|
+
- Handles `subscribe` and `unsubscribe` messages for pub/sub channels
|
|
52
|
+
- Extracts `type`, `data`, and `queryId` from the message
|
|
53
|
+
- Detects binary upload tags (`<!B>`, `<!A>`, `<!F>`) in the message
|
|
54
|
+
- Coordinates with `fileTransfer` to wait for HTTP uploads
|
|
55
|
+
- Injects uploaded binary data into the message at the tagged paths
|
|
56
|
+
- Invokes the appropriate controller with the complete message
|
|
57
|
+
- Handles errors and sends error responses back to the client
|
|
58
|
+
|
|
59
|
+
### `send.js`
|
|
60
|
+
|
|
61
|
+
Outgoing message handler that serializes and sends responses:
|
|
62
|
+
|
|
63
|
+
- Serializes controller return values using JSS encoding
|
|
64
|
+
- Detects Buffer/ArrayBuffer values in responses
|
|
65
|
+
- Replaces binary data with `<!L>` (link) tags for client download
|
|
66
|
+
- Registers binary data with `fileTransfer` for HTTP download
|
|
67
|
+
- Sends the serialized response with matching `queryId`
|
|
68
|
+
|
|
69
|
+
### `receiveContext.js`
|
|
70
|
+
|
|
71
|
+
Controller context factory that creates the `this` binding for controllers:
|
|
72
|
+
|
|
73
|
+
- Extracts session ID from request cookies
|
|
74
|
+
- Provides `broadcast()` and `broadcastOthers()` for messaging all clients
|
|
75
|
+
- Provides `publish()` for pub/sub channel messaging
|
|
76
|
+
- Exposes `clientId`, `sessionId`, and `clients` Map
|
|
77
|
+
- When auth is configured, adds `isAuthenticated`, `authTier`, `principal`, and `requiresTier()`
|
|
78
|
+
|
|
79
|
+
### `tagUtils.js`
|
|
80
|
+
|
|
81
|
+
Utilities for parsing and processing binary data tags:
|
|
82
|
+
|
|
83
|
+
- `findUploadTags(obj)` — Find all `<!B>` and `<!A>` tags with their paths
|
|
84
|
+
- `findFileTags(obj)` — Find all `<!F>` tags for streaming transfers
|
|
85
|
+
- `cleanUploadTags(obj)` — Remove tag suffixes from object keys
|
|
86
|
+
- `setValueAtPath(obj, path, value)` — Inject values at dot-notation paths
|
package/server/socket/open.js
CHANGED
|
@@ -1,10 +1,156 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Socket Open Handler - Connection Validation Gateway
|
|
3
|
+
*
|
|
4
|
+
* This module provides the entry point handler for new WebSocket connections.
|
|
5
|
+
* It acts as a security gateway, validating the connection origin and enforcing
|
|
6
|
+
* security policies before allowing the connection to proceed.
|
|
7
|
+
*
|
|
8
|
+
* ## Purpose
|
|
9
|
+
*
|
|
10
|
+
* When a new WebSocket connection is initiated, this handler:
|
|
11
|
+
* 1. Receives the socket and HTTP upgrade request
|
|
12
|
+
* 2. Delegates to origin security validation
|
|
13
|
+
* 3. Returns whether the connection should be accepted or rejected
|
|
14
|
+
*
|
|
15
|
+
* ## Security Flow
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* Client Connect → open() → originSecurity() → Accept/Reject
|
|
19
|
+
* ↓
|
|
20
|
+
* - Validates Origin header
|
|
21
|
+
* - Checks domain allowlist
|
|
22
|
+
* - Prevents CSRF attacks
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ## Integration
|
|
26
|
+
*
|
|
27
|
+
* This handler is called by the wiring layer when a new WebSocket
|
|
28
|
+
* connection is received. It's the first line of defense against
|
|
29
|
+
* unauthorized or malicious connections.
|
|
30
|
+
*
|
|
31
|
+
* @module server/socket/open
|
|
32
|
+
* @see {@link module:server/security/origin} - Origin validation implementation
|
|
33
|
+
* @see {@link module:server/lib/wiring} - Wiring layer that calls this handler
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Used internally by wiring layer
|
|
37
|
+
* const open = require('./socket/open')
|
|
38
|
+
*
|
|
39
|
+
* wss.on('connection', (socket, req) => {
|
|
40
|
+
* const isValid = open(socket, req, (error) => {
|
|
41
|
+
* console.error('Connection rejected:', error)
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* if (!isValid) {
|
|
45
|
+
* socket.close(1008, 'Security policy violation')
|
|
46
|
+
* return
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // Connection accepted, continue with setup...
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Error callback receives security violation details
|
|
54
|
+
* open(socket, req, (error) => {
|
|
55
|
+
* console.error('Security violation:', error.message)
|
|
56
|
+
* // Log for security monitoring
|
|
57
|
+
* securityLog.warn({
|
|
58
|
+
* type: 'connection_rejected',
|
|
59
|
+
* origin: req.headers.origin,
|
|
60
|
+
* ip: req.socket.remoteAddress,
|
|
61
|
+
* reason: error.message
|
|
62
|
+
* })
|
|
63
|
+
* })
|
|
64
|
+
*/
|
|
2
65
|
|
|
3
|
-
|
|
66
|
+
const originSecurity = require("../security/origin");
|
|
4
67
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Handle socket open event and validate connection security.
|
|
70
|
+
*
|
|
71
|
+
* This function is the first handler called when a new WebSocket
|
|
72
|
+
* connection is established. It validates the connection against
|
|
73
|
+
* security policies (primarily origin validation) and returns
|
|
74
|
+
* whether the connection should be accepted.
|
|
75
|
+
*
|
|
76
|
+
* ## Security Checks
|
|
77
|
+
*
|
|
78
|
+
* - **Origin Validation**: Ensures the Origin header matches allowed domains
|
|
79
|
+
* - **CSRF Prevention**: Blocks cross-origin requests from untrusted sources
|
|
80
|
+
* - **Protocol Compliance**: Validates WebSocket upgrade request format
|
|
81
|
+
*
|
|
82
|
+
* ## Failure Handling
|
|
83
|
+
*
|
|
84
|
+
* When validation fails:
|
|
85
|
+
* 1. The onError callback is invoked with error details
|
|
86
|
+
* 2. The function returns `false`
|
|
87
|
+
* 3. The caller is responsible for closing the socket
|
|
88
|
+
*
|
|
89
|
+
* ## Success Flow
|
|
90
|
+
*
|
|
91
|
+
* When validation succeeds:
|
|
92
|
+
* 1. The function returns `true`
|
|
93
|
+
* 2. The caller can proceed with connection setup
|
|
94
|
+
* 3. No error callback is invoked
|
|
95
|
+
*
|
|
96
|
+
* @function open
|
|
97
|
+
* @param {Object} socket - WebSocket instance (from WebSocketServer)
|
|
98
|
+
* @param {http.IncomingMessage} req - HTTP upgrade request object
|
|
99
|
+
* @param {function(Error): void} onError - Callback invoked on security failure
|
|
100
|
+
* @returns {boolean} True if connection is valid and secure, false otherwise
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // Basic usage in connection handler
|
|
104
|
+
* const isSecure = open(socket, req, (err) => {
|
|
105
|
+
* console.error('Connection failed security check:', err)
|
|
106
|
+
* })
|
|
107
|
+
*
|
|
108
|
+
* if (!isSecure) {
|
|
109
|
+
* return // Connection will be terminated
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* // Safe to proceed with connection setup
|
|
113
|
+
* setupConnection(socket, req)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // With detailed error handling
|
|
117
|
+
* const isSecure = open(socket, req, (error) => {
|
|
118
|
+
* // Log security event
|
|
119
|
+
* logger.security('connection_rejected', {
|
|
120
|
+
* origin: req.headers.origin,
|
|
121
|
+
* host: req.headers.host,
|
|
122
|
+
* ip: req.socket.remoteAddress,
|
|
123
|
+
* userAgent: req.headers['user-agent'],
|
|
124
|
+
* error: error.message
|
|
125
|
+
* })
|
|
126
|
+
*
|
|
127
|
+
* // Optionally notify security monitoring
|
|
128
|
+
* if (isRateLimitExceeded(req.socket.remoteAddress)) {
|
|
129
|
+
* alertSecurityTeam('Possible attack detected')
|
|
130
|
+
* }
|
|
131
|
+
* })
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* // In wiring layer integration
|
|
135
|
+
* function handleConnection(socket, req) {
|
|
136
|
+
* // First, validate security
|
|
137
|
+
* if (!open(socket, req, handleSecurityError)) {
|
|
138
|
+
* socket.close(1008, 'Policy violation')
|
|
139
|
+
* return null
|
|
140
|
+
* }
|
|
141
|
+
*
|
|
142
|
+
* // Connection is secure, create session
|
|
143
|
+
* const session = createSession(socket, req)
|
|
144
|
+
* return session
|
|
145
|
+
* }
|
|
146
|
+
*/
|
|
147
|
+
module.exports = function open(socket, req, onError) {
|
|
148
|
+
// Delegate to origin security for validation
|
|
149
|
+
const isSecure = originSecurity(socket, req, onError);
|
|
150
|
+
|
|
151
|
+
if (!isSecure) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
};
|