-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.ts
executable file
·354 lines (293 loc) · 10 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
import {Readable} from "node:stream"
export * from "./util";
export {WeakValueMap} from "./WeakValueMap";
import {BreakPoints} from "./BreakPoints";
export * from "./hybridstreams";
export const _testForRaceCondition_breakPoints = new BreakPoints();
export type UploadFile = {
createReadStream: () => Readable
}
export type CSRFProtectionMode = "preflight" | "corsReadToken" | "csrfToken"
export type SecurityPropertiesOfHttpRequest = {
httpMethod: string,
/**
* Computed origin
* @see getOrigin
*/
origin?: string,
/**
* Computed destination
* @see getDestination
*/
destination?: string,
/**
* Computed result from Util.js/browserMightHaveSecurityIssuseWithCrossOriginRequests function
*/
browserMightHaveSecurityIssuseWithCrossOriginRequests: Boolean;
csrfProtectionMode?: CSRFProtectionMode
corsReadToken?: string,
csrfToken?: string,
/**
* We are sure that the client proved to make a successful http read and present the result while the call had these security properties.
* Used for socket connection.
*/
readWasProven?: boolean
/**
* Computed result from couldBeSimpleRequest function
*/
couldBeSimpleRequest?: boolean
};
/**
* The info that's needed to set up a socket connection
*/
export type WelcomeInfo = {
classId: string,
/**
* Undefined, if the server does not support engine.io
*/
engineIoPath?: string
};
export interface IServerSession {
/**
* The client needs to know some things, before creating the socket connection
*/
getWelcomeInfo(): WelcomeInfo;
getCookieSession(encryptedQuestion: ServerPrivateBox<GetCookieSession_question>): {state: CookieSessionState, token: ServerPrivateBox<GetCookieSessionAnswerToken>};
getHttpSecurityProperties(encryptedQuestion: ServerPrivateBox<GetHttpSecurityProperties_question>): ServerPrivateBox<GetHttpSecurityProperties_answer>;
getCorsReadToken(): string
updateCookieSession(encryptedCookieSessionUpdate: ServerPrivateBox<CookieSessionUpdate>, alsoReturnNewSession: ServerPrivateBox<GetCookieSession_question>) : Promise<{state: CookieSessionState, token: ServerPrivateBox<GetCookieSessionAnswerToken>}>;
}
export interface CookieSession extends Record<string, unknown> {
/**
* Security: This may be readable from inside the browser.
*/
id: string
version: number
/**
* Branch protection salt. We safe and check this also in the SessionValidator
* Because the commit to the SessionValidator does not happen at instant but before the next call, an attacker could use 2 socket connections and create 2 (/multiple) branches of CookieSessions with different content but (each time) the same version number.
* <p>
* More detail: To create such branching, one would have 2 socket connections, call a session-changing method, get the returned CookieSessionUpdate token and http-call the updateCookieSession() method with it **simultaneously**
* Should be possible in a browser (cross-site attacking scenario, when allowed for a subset of ServerSessions) and no problem with a non-browser client.
* </p>
*/
bpSalt?: string
/**
* To allow for a more stupid SessionValidator that just stores [id]_[version]_[bpSalt] entry strings
*/
previousBpSalt?: string
commandDestruction?:boolean
}
/**
* Sent as a public cookie and header for change-indication / detection by the socket clients.
* See tests/session-playground
*/
export type CookieSessionState = Pick<CookieSession, "id" | "version"> | undefined; // undefined means as always: no cookie set / session not created; Do we need the bpSalt ? or is that too much bloat ?
export type Socket_Client2ServerMessage = {
/**
* Incremented on every call (by the client) to help put things in the proper timely order and prevent race condition bugs.
*/
sequenceNumber: number
type: "methodCall" | "getVersion" | "updateHttpSecurityProperties" | "setCookieSession" | "methodDownCallResult" | "channelItemNotUsedAnymore" | "streamDataRequest" | "streamData";
payload: Socket_MethodCall | Socket_ChannelItemNotUsedAnymore | Socket_StreamDataRequest | Socket_StreamData | unknown
}
export type Socket_MethodCall = {
/**
* Generated by the client
*/
callId: number
serverSessionClassId?: string
methodName: string,
args: unknown[]
}
export type Socket_Server2ClientMessage = {
/**
* Incremented on every call (by the client) to help put things in the proper timely order and prevent race condition bugs.
*/
sequenceNumber: number
type: "init" | "methodCallResult" | "getVersion" | "downCall" | "channelItemNotUsedAnymore" | "streamDataRequest" | "streamData"
payload: Socket_Server2ClientInit | Socket_MethodUpCallResult | Socket_DownCall | Socket_StreamDataRequest | Socket_StreamData | Socket_ChannelItemNotUsedAnymore
}
/**
* Result of a client->server call
*/
export type Socket_MethodUpCallResult = {
/**
* Generated by the client
*/
callId: number
result?: unknown
/**
* Behaves as close as possible to the http api
*/
status: 200 | 500 | 550 | "needsHttpSecurityProperties" | "needsCookieSession" | "doCookieSessionUpdate" | "dropped_CookieSessionIsOutdated"
/**
* If a CommunicationError was thrown, then that code
*/
httpStatusCode?: number
/**
* If an error occurred
*/
error?: object
/**
* If this is needed to proceed the call
*/
needsHttpSecurityProperties?: {
question: ServerPrivateBox<GetHttpSecurityProperties_question>
/**
* Hint for the client: The ServerSessionClass id or the security group id for which the answer is valid.
*/
syncKey: string
}
/**
* Request the cookie-session from the server by http
*/
needsInitializedCookieSession?: ServerPrivateBox<GetCookieSession_question>
/**
* Commands to update the cookieSession via http (so it really gets stored in the cookie)
*/
doCookieSessionUpdate?: ServerPrivateBox<CookieSessionUpdate>
}
export type Socket_Server2ClientInit = {
cookieSessionRequest: ServerPrivateBox<GetCookieSession_question>
}
/**
* Question from the websocket connection
*/
export type GetHttpSecurityProperties_question = {
serverSocketConnectionId: string
serverSessionClassId: string,
}
export type GetHttpSecurityProperties_answer = {
question: GetHttpSecurityProperties_question,
result: SecurityPropertiesOfHttpRequest
}
/**
* Question from the websocket connection
*/
export type GetCookieSession_question = {
serverSocketConnectionId: string
forceInitialize: boolean;
}
export type GetCookieSessionAnswerToken = {
question: GetCookieSession_question,
/**
* Can be undefined, if no session was started yet
*/
cookieSession?: CookieSession
}
/**
* Update the cookie session on the http side
*/
export type CookieSessionUpdate = {
/**
* Where did this come from ?
*/
serverSessionClassId: string,
newSession: CookieSession
}
/**
* The content is encrypted and can only be read by this server, or another server that shares {@link Server#secret}
* <p>
* Encrypted (+MAC'ed) value in a box that points to the correct secret key that should be used to decrypt it.
* </p>
* - The box was encrypted by the server and it's authenticity can be trusted. It's meant to be decrypted by the server again / the client can't see the content.
* - It stores a content type to prevent spoofing with a token on stock with a different types
*
*/
export type ServerPrivateBox<Content> = {
/**
* Base64 encoded.
* <p>
* <i>NOTE: Is a nonce really needed or could we save us these 24 bytes here? It won't prevent replay attacks, if handed to the client (restfuncs is aware of replay and takse other measures against these).
* Also data variety is enough by the current use cases (i.e. there's the ServerSocketConnection id included) and the information that 2 tokens have the same value does not hurt.
* But for cryptographic cleanliness we leave it in.
* </i>
* </p>
*/
nonce: string
/**
* Encrypted content, base64 encoded
*/
content: string;
/**
* Fake property. To make ServerPrivateBox typesafe, we must reference 'Content' somewhere. Will never be set
*/
_contentType?: Content
}
export type ChannelItemDTO = {
_dtoType: "ClientCallback" | "UploadFile" | "Readable"
/**
* Chosen by the sender (usually the client)
*/
id: number;
}
export type ClientCallbackDTO = ChannelItemDTO & {
_dtoType: "ClientCallback",
}
/**
* The server calls down a callback function
*/
export type Socket_DownCall = {
id: number;
/**
* Client's id
*/
callbackFnId: number
args: unknown[]
serverAwaitsAnswer: boolean;
/**
* Whether the callback function was declared as non-void
*/
diagnosis_resultWasDeclared: boolean;
}
export type Socket_ChannelItemNotUsedAnymore = {
id: number;
/**
* Include lastSequenceNumberFromClient
*/
time: number;
}
export type Socket_StreamDataRequest = {
/**
* Id of the channel item (the Readable)
*/
id: number;
/**
* Like in Readable._read
*/
size: number;
}
export type Socket_StreamData = {
/**
* Id of the channel item (the Readable / Writable)
*/
id: number;
/**
* String or base64 encoded string from buffer, or null when the end of the stream is reached / stream is closed
* TODO: Implement better binary protocol
*/
data: string | null;
/**
* When set to "buffer", then data is a buffer, encoded as base64 string
*/
encoding: BufferEncoding | string
/**
* If the stream errored
*/
error?: object
}
/**
* Result of a server->client callback function call
*/
export type Socket_DownCallResult = {
/**
* Generated by the server
*/
callId: number
result?: unknown
/**
* If an error occurred
*/
error?: object
}