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
package/server/security/reply.js
CHANGED
|
@@ -1,21 +1,279 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Replay Attack Prevention for api-ape Server
|
|
3
|
+
*
|
|
4
|
+
* This module provides protection against replay attacks on WebSocket messages.
|
|
5
|
+
* Replay attacks occur when an attacker intercepts valid messages and resends
|
|
6
|
+
* them to perform unauthorized actions.
|
|
7
|
+
*
|
|
8
|
+
* ## How Replay Attacks Work
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* Normal Request:
|
|
12
|
+
* Client ──────► Server
|
|
13
|
+
* { type: '/transfer', data: { amount: 100 } }
|
|
14
|
+
*
|
|
15
|
+
* Replay Attack:
|
|
16
|
+
* Attacker captures message and resends it multiple times:
|
|
17
|
+
* Attacker ──────► Server (same message)
|
|
18
|
+
* Attacker ──────► Server (same message)
|
|
19
|
+
* Attacker ──────► Server (same message)
|
|
20
|
+
* Result: Multiple transfers instead of one!
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## Protection Mechanism
|
|
24
|
+
*
|
|
25
|
+
* This module prevents replay attacks using two strategies:
|
|
26
|
+
*
|
|
27
|
+
* ### 1. Request ID Tracking
|
|
28
|
+
*
|
|
29
|
+
* Each message has a unique queryId generated from its content hash.
|
|
30
|
+
* The server tracks recently seen queryIds and rejects duplicates.
|
|
31
|
+
*
|
|
32
|
+
* ```
|
|
33
|
+
* Request 1: queryId = "K7M3NP" ✓ (new, allowed)
|
|
34
|
+
* Request 2: queryId = "K7M3NP" ✗ (duplicate, rejected)
|
|
35
|
+
* Request 3: queryId = "X9W2QR" ✓ (new, allowed)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ### 2. Timestamp Validation
|
|
39
|
+
*
|
|
40
|
+
* Messages include a `createdAt` timestamp. The server rejects messages that:
|
|
41
|
+
* - Are too old (> 10 seconds in the past)
|
|
42
|
+
* - Are from the future (clock skew attack)
|
|
43
|
+
*
|
|
44
|
+
* ```
|
|
45
|
+
* Server Time: 12:00:00
|
|
46
|
+
*
|
|
47
|
+
* createdAt: 11:59:55 ✓ (5 seconds ago, valid)
|
|
48
|
+
* createdAt: 11:59:45 ✗ (15 seconds ago, too old)
|
|
49
|
+
* createdAt: 12:00:05 ✗ (5 seconds in future, invalid)
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* ## Window Management
|
|
53
|
+
*
|
|
54
|
+
* The request tracking window is limited to 10 seconds worth of requests.
|
|
55
|
+
* Older entries are automatically purged to prevent memory growth.
|
|
56
|
+
*
|
|
57
|
+
* ```
|
|
58
|
+
* ┌──────────────────────────────────────────────────────────────┐
|
|
59
|
+
* │ 10-second Window │
|
|
60
|
+
* │ │
|
|
61
|
+
* │ [old requests purged] ← ─ ─ ─ [tracked requests] ─ ─ ─ → [now] │
|
|
62
|
+
* │ │
|
|
63
|
+
* │ Requests older than 10 seconds are removed from tracking │
|
|
64
|
+
* │ New duplicates within window are rejected │
|
|
65
|
+
* └──────────────────────────────────────────────────────────────┘
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* ## Per-Connection Isolation
|
|
69
|
+
*
|
|
70
|
+
* Each WebSocket connection gets its own replay checker instance.
|
|
71
|
+
* This provides:
|
|
72
|
+
* - Isolation between clients (one client's requests don't affect another)
|
|
73
|
+
* - Automatic cleanup when connection closes
|
|
74
|
+
* - Memory efficiency (only tracks requests for active connections)
|
|
75
|
+
*
|
|
76
|
+
* @module server/security/reply
|
|
77
|
+
* @see {@link module:server/socket/receive} for usage in message handling
|
|
78
|
+
* @see {@link module:utils/messageHash} for queryId generation
|
|
79
|
+
*
|
|
80
|
+
* @example <caption>Creating a Replay Checker</caption>
|
|
81
|
+
* const replySecurity = require('./reply')
|
|
82
|
+
*
|
|
83
|
+
* // Create checker for a connection
|
|
84
|
+
* const checkReply = replySecurity()
|
|
85
|
+
*
|
|
86
|
+
* // In message handler
|
|
87
|
+
* try {
|
|
88
|
+
* checkReply(queryId, createdAt)
|
|
89
|
+
* // Request is valid - process it
|
|
90
|
+
* } catch (err) {
|
|
91
|
+
* // Request is invalid - replay or expired
|
|
92
|
+
* console.error('Replay attack or stale request:', err.message)
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* @example <caption>Integration with Socket Receive</caption>
|
|
96
|
+
* // In wiring.js
|
|
97
|
+
* const checkReply = replySecurity()
|
|
98
|
+
*
|
|
99
|
+
* socket.on('message', async (msg) => {
|
|
100
|
+
* const { queryId, createdAt, type, data } = parseMessage(msg)
|
|
101
|
+
*
|
|
102
|
+
* try {
|
|
103
|
+
* checkReply(queryId, createdAt)
|
|
104
|
+
* const result = await controllers[type](data)
|
|
105
|
+
* send(queryId, null, result, null)
|
|
106
|
+
* } catch (err) {
|
|
107
|
+
* send(queryId, null, null, err)
|
|
108
|
+
* }
|
|
109
|
+
* })
|
|
110
|
+
*
|
|
111
|
+
* @example <caption>Error Messages</caption>
|
|
112
|
+
* // Future timestamp
|
|
113
|
+
* checkReply('abc', Date.now() + 60000)
|
|
114
|
+
* // Throws: "createdAt ahead of server by 60 secs"
|
|
115
|
+
*
|
|
116
|
+
* // Old timestamp
|
|
117
|
+
* checkReply('abc', Date.now() - 30000)
|
|
118
|
+
* // Throws: "request is old by 30 secs"
|
|
119
|
+
*
|
|
120
|
+
* // Duplicate request
|
|
121
|
+
* checkReply('abc', Date.now()) // First call - OK
|
|
122
|
+
* checkReply('abc', Date.now()) // Second call
|
|
123
|
+
* // Throws: "Reply: abc"
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a replay attack checker for a WebSocket connection
|
|
128
|
+
*
|
|
129
|
+
* Returns a function that validates incoming requests against:
|
|
130
|
+
* - Previously seen request IDs (prevents duplicates)
|
|
131
|
+
* - Request timestamps (prevents stale/future requests)
|
|
132
|
+
*
|
|
133
|
+
* Each checker maintains its own request history, isolated from other
|
|
134
|
+
* connections. The history is automatically pruned to only keep requests
|
|
135
|
+
* from the last 10 seconds.
|
|
136
|
+
*
|
|
137
|
+
* @returns {function(string, number): void} Check function that throws on invalid requests
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* // Create a checker for this connection
|
|
141
|
+
* const checkReply = replySecurity()
|
|
142
|
+
*
|
|
143
|
+
* // Check a request (throws if invalid)
|
|
144
|
+
* checkReply('queryId123', Date.now())
|
|
145
|
+
*
|
|
146
|
+
* // Same queryId again - throws
|
|
147
|
+
* try {
|
|
148
|
+
* checkReply('queryId123', Date.now())
|
|
149
|
+
* } catch (err) {
|
|
150
|
+
* console.log(err.message) // "Reply: queryId123"
|
|
151
|
+
* }
|
|
152
|
+
*/
|
|
153
|
+
module.exports = function () {
|
|
154
|
+
/**
|
|
155
|
+
* Array of recently seen requests
|
|
156
|
+
*
|
|
157
|
+
* Each entry is a tuple of [queryId, createdAt timestamp].
|
|
158
|
+
* Entries older than 10 seconds are pruned on each check.
|
|
159
|
+
*
|
|
160
|
+
* @type {Array<[string, number]>}
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
let requestCheck = [];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validate a request against replay attacks
|
|
167
|
+
*
|
|
168
|
+
* Performs three validations:
|
|
169
|
+
* 1. **Future check**: Rejects if createdAt is ahead of server time
|
|
170
|
+
* 2. **Staleness check**: Rejects if createdAt is more than 10 seconds old
|
|
171
|
+
* 3. **Duplicate check**: Rejects if queryId was seen in the last 10 seconds
|
|
172
|
+
*
|
|
173
|
+
* If validation passes, the request is added to the tracking list.
|
|
174
|
+
*
|
|
175
|
+
* ## Time Tolerance
|
|
176
|
+
*
|
|
177
|
+
* The 10-second window provides:
|
|
178
|
+
* - Tolerance for minor network latency
|
|
179
|
+
* - Tolerance for minor clock skew between client and server
|
|
180
|
+
* - Protection against replay attacks (attacker must replay within 10 seconds)
|
|
181
|
+
* - Memory efficiency (limited tracking window)
|
|
182
|
+
*
|
|
183
|
+
* @param {string} queryId - Unique request identifier (hash of message content)
|
|
184
|
+
* @param {number} createdAt - Timestamp when the request was created (client-side)
|
|
185
|
+
* @throws {Error} If request is from the future (clock skew)
|
|
186
|
+
* @throws {Error} If request is too old (> 10 seconds)
|
|
187
|
+
* @throws {Error} If request is a duplicate (same queryId within window)
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // Valid request
|
|
191
|
+
* checkReply('K7M3NP2Q', Date.now() - 1000) // 1 second ago - OK
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* // Future request (clock skew or tampering)
|
|
195
|
+
* checkReply('K7M3NP2Q', Date.now() + 5000)
|
|
196
|
+
* // Error: "createdAt ahead of server by 5 secs"
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* // Stale request
|
|
200
|
+
* checkReply('K7M3NP2Q', Date.now() - 15000)
|
|
201
|
+
* // Error: "request is old by 15 secs"
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Duplicate request (replay attack)
|
|
205
|
+
* checkReply('K7M3NP2Q', Date.now()) // First - OK
|
|
206
|
+
* checkReply('K7M3NP2Q', Date.now()) // Second - Error
|
|
207
|
+
* // Error: "Reply: K7M3NP2Q"
|
|
208
|
+
*/
|
|
209
|
+
return (queryId, createdAt) => {
|
|
210
|
+
/**
|
|
211
|
+
* Current server timestamp
|
|
212
|
+
* @type {number}
|
|
213
|
+
*/
|
|
4
214
|
const startTime = Date.now();
|
|
5
|
-
|
|
6
|
-
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check 1: Reject requests from the future
|
|
218
|
+
*
|
|
219
|
+
* This catches:
|
|
220
|
+
* - Client clock running ahead
|
|
221
|
+
* - Timestamp manipulation attacks
|
|
222
|
+
* - Replay of pre-generated future requests
|
|
223
|
+
*/
|
|
224
|
+
if (createdAt > startTime) {
|
|
225
|
+
const skewSeconds = (createdAt - startTime) / 1000;
|
|
226
|
+
throw new Error(
|
|
227
|
+
`createdAt ahead of server by ${skewSeconds} secs. Request rejected.`,
|
|
228
|
+
);
|
|
7
229
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Calculate the cutoff time (10 seconds ago)
|
|
233
|
+
* Requests older than this are considered stale
|
|
234
|
+
* @type {number}
|
|
235
|
+
*/
|
|
236
|
+
const tenSecAgo = startTime - 10000;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check 2: Reject stale requests
|
|
240
|
+
*
|
|
241
|
+
* This catches:
|
|
242
|
+
* - Slow replay attacks
|
|
243
|
+
* - Network issues causing extreme delays
|
|
244
|
+
* - Replay of old captured requests
|
|
245
|
+
*/
|
|
246
|
+
if (createdAt < tenSecAgo) {
|
|
247
|
+
const staleSeconds = (startTime - createdAt) / 1000;
|
|
248
|
+
throw new Error(
|
|
249
|
+
`request is old by ${staleSeconds} secs. Request rejected.`,
|
|
250
|
+
);
|
|
11
251
|
}
|
|
12
|
-
|
|
13
|
-
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check 3: Reject duplicates and prune old entries
|
|
255
|
+
*
|
|
256
|
+
* Filter the request history to:
|
|
257
|
+
* 1. Check if this queryId was already seen (throws if found)
|
|
258
|
+
* 2. Remove entries older than 10 seconds (memory cleanup)
|
|
259
|
+
*
|
|
260
|
+
* This single pass efficiently handles both operations.
|
|
261
|
+
*/
|
|
262
|
+
requestCheck = requestCheck.filter(([passQueryId, createdWhen]) => {
|
|
263
|
+
// If we find the same queryId, it's a replay attack
|
|
14
264
|
if (passQueryId === queryId) {
|
|
15
|
-
throw new Error(`Reply: ${queryId}
|
|
265
|
+
throw new Error(`Reply: ${queryId} - Duplicate request rejected.`);
|
|
16
266
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
267
|
+
// Keep entries that are still within the 10-second window
|
|
268
|
+
return createdWhen > tenSecAgo;
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Request passed all checks - add to tracking list
|
|
273
|
+
*
|
|
274
|
+
* Store [queryId, createdAt] for future duplicate detection.
|
|
275
|
+
* This entry will be automatically pruned after 10 seconds.
|
|
276
|
+
*/
|
|
277
|
+
requestCheck.push([queryId, createdAt]);
|
|
278
|
+
};
|
|
279
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Socket Module
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The socket 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.
|
|
6
|
+
|
|
7
|
+
**Key capabilities:**
|
|
8
|
+
|
|
9
|
+
- **Connection validation** — Verify origin headers and enforce security policies before accepting connections
|
|
10
|
+
- **Message parsing** — Deserialize incoming WebSocket messages with JSS encoding support
|
|
11
|
+
- **Controller routing** — Route messages to the appropriate controller based on the `type` field
|
|
12
|
+
- **Pub/sub handling** — Process `subscribe` and `unsubscribe` messages for channel subscriptions
|
|
13
|
+
- **Binary data coordination** — Detect upload tags, wait for HTTP uploads, and inject binary data into messages
|
|
14
|
+
- **Response serialization** — Serialize controller responses and handle binary data with download links
|
|
15
|
+
- **Request correlation** — Match responses to requests via `queryId` for Promise resolution on the client
|
|
16
|
+
|
|
17
|
+
This module is the bridge between raw WebSocket frames and the high-level controller functions that developers write.
|
|
18
|
+
|
|
19
|
+
> **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
|
|
20
|
+
|
|
21
|
+
## Message Flow
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ Incoming Message │
|
|
26
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
27
|
+
│ │
|
|
28
|
+
│ WebSocket Frame │
|
|
29
|
+
│ │ │
|
|
30
|
+
│ ▼ │
|
|
31
|
+
│ receive.js │
|
|
32
|
+
│ │ Parse JSS → { type, data, queryId } │
|
|
33
|
+
│ │ Find upload tags → [{ path, hash, tag }] │
|
|
34
|
+
│ │ Wait for HTTP uploads (if any) │
|
|
35
|
+
│ │ Inject binary data at paths │
|
|
36
|
+
│ ▼ │
|
|
37
|
+
│ Controller │
|
|
38
|
+
│ │ this = { clientId, sessionId, broadcast, ...embed } │
|
|
39
|
+
│ │ return result │
|
|
40
|
+
│ ▼ │
|
|
41
|
+
│ send.js │
|
|
42
|
+
│ │ Detect Buffers → register downloads → add <!L> tags │
|
|
43
|
+
│ │ Serialize with JSS │
|
|
44
|
+
│ ▼ │
|
|
45
|
+
│ WebSocket Frame → Client │
|
|
46
|
+
│ │
|
|
47
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Authentication & Authorization
|
|
51
|
+
|
|
52
|
+
The socket module integrates with the auth system to enforce access control:
|
|
53
|
+
|
|
54
|
+
### Authorization Middleware
|
|
55
|
+
|
|
56
|
+
The `authMiddleware.js` module checks tier and permission requirements before controller dispatch:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const { createAuthMiddleware } = require('api-ape/server/socket/authMiddleware');
|
|
60
|
+
|
|
61
|
+
const authMiddleware = createAuthMiddleware({
|
|
62
|
+
requirements: {
|
|
63
|
+
'admin/*': { tier: 2, permissions: ['admin.access'] },
|
|
64
|
+
'user/*': { tier: 1 },
|
|
65
|
+
'public/*': { tier: 0 }
|
|
66
|
+
},
|
|
67
|
+
defaultTier: 0,
|
|
68
|
+
requireAuthByDefault: false
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Controller Context
|
|
73
|
+
|
|
74
|
+
When auth is configured, controllers have access to auth state via `this`:
|
|
75
|
+
|
|
76
|
+
| Property | Type | Description |
|
|
77
|
+
|----------|------|-------------|
|
|
78
|
+
| `this.isAuthenticated` | `boolean` | Whether socket is authenticated (Tier ≥ 1) |
|
|
79
|
+
| `this.authTier` | `number` | Current tier (0-3) |
|
|
80
|
+
| `this.principal` | `object\|null` | User info: `{ userId, roles, permissions }` |
|
|
81
|
+
| `this.authState` | `object\|null` | Full auth state object |
|
|
82
|
+
| `this.requiresTier(n)` | `function` | Check if socket meets minimum tier |
|
|
83
|
+
|
|
84
|
+
### Message Flow with Auth
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
88
|
+
│ Incoming Message │
|
|
89
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
90
|
+
│ │
|
|
91
|
+
│ WebSocket Frame │
|
|
92
|
+
│ │ │
|
|
93
|
+
│ ▼ │
|
|
94
|
+
│ receive.js │
|
|
95
|
+
│ │ Parse JSS → { type, data, queryId } │
|
|
96
|
+
│ │ │
|
|
97
|
+
│ │ ── Is auth message? ────────────────────────────────── │
|
|
98
|
+
│ │ │ YES: Route to auth handler (opaque_*, mfa_*) │
|
|
99
|
+
│ │ │ NO: Continue to authorization │
|
|
100
|
+
│ │ ▼ │
|
|
101
|
+
│ │ ── Authorization check ─────────────────────────────── │
|
|
102
|
+
│ │ │ FAIL: Return authz_fail │
|
|
103
|
+
│ │ │ PASS: Continue to controller │
|
|
104
|
+
│ ▼ │
|
|
105
|
+
│ Controller │
|
|
106
|
+
│ │ this = { clientId, isAuthenticated, principal, ... } │
|
|
107
|
+
│ ▼ │
|
|
108
|
+
│ send.js → Client │
|
|
109
|
+
│ │
|
|
110
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## See Also
|
|
114
|
+
|
|
115
|
+
- [`../lib/wiring.js`](../lib/wiring.js) — WebSocket connection setup that calls these handlers
|
|
116
|
+
- [`../lib/fileTransfer.js`](../lib/fileTransfer.js) — Binary file transfer coordination
|
|
117
|
+
- [`../security/README.md`](../security/README.md) — Origin validation used by `open.js`
|
|
118
|
+
- [`../security/auth/README.md`](../security/auth/README.md) — Full authentication documentation
|
|
119
|
+
- [`tagUtils.js` JSDoc](./tagUtils.js) — Detailed tag system documentation
|