@floegence/flowersec-core 0.4.0 → 0.6.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.
|
@@ -13,11 +13,20 @@ export async function connectCore(args) {
|
|
|
13
13
|
const observer = normalizeObserver(args.opts.observer);
|
|
14
14
|
const signal = args.opts.signal;
|
|
15
15
|
const connectStart = nowSeconds();
|
|
16
|
-
const origin = args.opts.origin;
|
|
17
|
-
if (origin
|
|
16
|
+
const origin = typeof args.opts.origin === "string" ? args.opts.origin.trim() : "";
|
|
17
|
+
if (origin === "") {
|
|
18
18
|
throw new FlowersecError({ path: args.path, stage: "validate", code: "missing_origin", message: "missing origin" });
|
|
19
19
|
}
|
|
20
|
-
if (args.
|
|
20
|
+
if (args.path === "tunnel" && args.attach == null) {
|
|
21
|
+
throw new FlowersecError({
|
|
22
|
+
path: args.path,
|
|
23
|
+
stage: "validate",
|
|
24
|
+
code: "invalid_option",
|
|
25
|
+
message: "missing attach payload",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const wsUrl = typeof args.wsUrl === "string" ? args.wsUrl.trim() : "";
|
|
29
|
+
if (wsUrl === "") {
|
|
21
30
|
const code = args.path === "tunnel" ? "missing_tunnel_url" : "missing_ws_url";
|
|
22
31
|
throw new FlowersecError({ path: args.path, stage: "validate", code, message: "missing websocket url" });
|
|
23
32
|
}
|
|
@@ -56,9 +65,28 @@ export async function connectCore(args) {
|
|
|
56
65
|
if (!Number.isSafeInteger(maxWsQueuedBytes) || maxWsQueuedBytes < 0) {
|
|
57
66
|
invalidOption("maxWsQueuedBytes must be a non-negative integer");
|
|
58
67
|
}
|
|
68
|
+
const channelId = typeof args.channelId === "string" ? args.channelId.trim() : "";
|
|
69
|
+
if (channelId === "") {
|
|
70
|
+
throw new FlowersecError({ path: args.path, stage: "validate", code: "missing_channel_id", message: "missing channel_id" });
|
|
71
|
+
}
|
|
72
|
+
let psk;
|
|
73
|
+
try {
|
|
74
|
+
const pskB64u = typeof args.e2eePskB64u === "string" ? args.e2eePskB64u.trim() : "";
|
|
75
|
+
psk = base64urlDecode(pskB64u);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_psk", message: "invalid e2ee_psk_b64u", cause: e });
|
|
79
|
+
}
|
|
80
|
+
if (psk.length !== 32) {
|
|
81
|
+
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_psk", message: "psk must be 32 bytes" });
|
|
82
|
+
}
|
|
83
|
+
const suite = args.defaultSuite;
|
|
84
|
+
if (suite !== 1 && suite !== 2) {
|
|
85
|
+
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_suite", message: "invalid suite" });
|
|
86
|
+
}
|
|
59
87
|
let ws;
|
|
60
88
|
try {
|
|
61
|
-
ws = createWebSocket(
|
|
89
|
+
ws = createWebSocket(wsUrl, origin, args.opts.wsFactory);
|
|
62
90
|
}
|
|
63
91
|
catch (e) {
|
|
64
92
|
if (e instanceof OriginMismatchError) {
|
|
@@ -103,14 +131,6 @@ export async function connectCore(args) {
|
|
|
103
131
|
}
|
|
104
132
|
throwIfAborted(signal, "connect aborted");
|
|
105
133
|
if (args.path === "tunnel") {
|
|
106
|
-
if (args.attach == null) {
|
|
107
|
-
throw new FlowersecError({
|
|
108
|
-
path: args.path,
|
|
109
|
-
stage: "validate",
|
|
110
|
-
code: "invalid_option",
|
|
111
|
-
message: "missing attach payload",
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
134
|
try {
|
|
115
135
|
ws.send(args.attach.attachJson);
|
|
116
136
|
}
|
|
@@ -125,32 +145,11 @@ export async function connectCore(args) {
|
|
|
125
145
|
throw new FlowersecError({ path: args.path, stage: "attach", code: "attach_failed", message: "attach failed", cause: err });
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
|
-
let psk;
|
|
129
|
-
try {
|
|
130
|
-
psk = base64urlDecode(args.e2eePskB64u);
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
transport.close();
|
|
134
|
-
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_psk", message: "invalid e2ee_psk_b64u", cause: e });
|
|
135
|
-
}
|
|
136
|
-
if (psk.length !== 32) {
|
|
137
|
-
transport.close();
|
|
138
|
-
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_psk", message: "psk must be 32 bytes" });
|
|
139
|
-
}
|
|
140
|
-
if (args.channelId == null || args.channelId === "") {
|
|
141
|
-
transport.close();
|
|
142
|
-
throw new FlowersecError({ path: args.path, stage: "validate", code: "missing_channel_id", message: "missing channel_id" });
|
|
143
|
-
}
|
|
144
|
-
const suite = args.defaultSuite;
|
|
145
|
-
if (suite !== 1 && suite !== 2) {
|
|
146
|
-
transport.close();
|
|
147
|
-
throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_suite", message: "invalid suite" });
|
|
148
|
-
}
|
|
149
148
|
const handshakeStart = nowSeconds();
|
|
150
149
|
let secure;
|
|
151
150
|
try {
|
|
152
151
|
secure = await withAbortAndTimeout(clientHandshake(transport, {
|
|
153
|
-
channelId
|
|
152
|
+
channelId,
|
|
154
153
|
suite,
|
|
155
154
|
psk,
|
|
156
155
|
clientFeatures,
|
|
@@ -386,6 +385,11 @@ export async function connectCore(args) {
|
|
|
386
385
|
cause: err,
|
|
387
386
|
});
|
|
388
387
|
}
|
|
388
|
+
if (signal != null && abortListener != null) {
|
|
389
|
+
// AbortSignal is only used to cancel the open + StreamHello phase.
|
|
390
|
+
// After the stream is ready, callers should close/reset it explicitly.
|
|
391
|
+
signal.removeEventListener("abort", abortListener);
|
|
392
|
+
}
|
|
389
393
|
return s;
|
|
390
394
|
},
|
|
391
395
|
close: closeAll,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const tunnelAttachCloseReasons: readonly ["too_many_connections", "expected_attach", "invalid_attach", "invalid_token", "channel_mismatch", "init_exp_mismatch", "idle_timeout_mismatch", "role_mismatch", "token_replay", "replace_rate_limited", "attach_failed"];
|
|
1
|
+
export declare const tunnelAttachCloseReasons: readonly ["too_many_connections", "expected_attach", "invalid_attach", "invalid_token", "channel_mismatch", "init_exp_mismatch", "idle_timeout_mismatch", "role_mismatch", "token_replay", "replace_rate_limited", "attach_failed", "timeout", "canceled"];
|
|
2
2
|
export type TunnelAttachCloseReason = (typeof tunnelAttachCloseReasons)[number];
|
|
3
3
|
export declare function isTunnelAttachCloseReason(v: string | undefined): v is TunnelAttachCloseReason;
|
|
@@ -2,7 +2,7 @@ import type { ClientPath } from "../client.js";
|
|
|
2
2
|
export type ConnectResult = "ok" | "fail";
|
|
3
3
|
export type ConnectReason = "websocket_error" | "websocket_closed" | "timeout" | "canceled";
|
|
4
4
|
export type AttachResult = "ok" | "fail";
|
|
5
|
-
export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "replace_rate_limited" | "attach_failed";
|
|
5
|
+
export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "replace_rate_limited" | "attach_failed" | "timeout" | "canceled";
|
|
6
6
|
export type HandshakeResult = "ok" | "fail";
|
|
7
7
|
export type HandshakeReason = "auth_tag_mismatch" | "handshake_failed" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
|
|
8
8
|
export type WsCloseKind = "local" | "peer_or_error";
|
|
@@ -80,13 +80,16 @@ export async function connectTunnel(grant, opts) {
|
|
|
80
80
|
catch (e) {
|
|
81
81
|
throw new FlowersecError({ stage: "validate", code: "invalid_input", path: "tunnel", message: "invalid ChannelInitGrant", cause: e });
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
const tunnelUrl = checkedGrant.tunnel_url.trim();
|
|
84
|
+
if (tunnelUrl === "") {
|
|
84
85
|
throw new FlowersecError({ stage: "validate", code: "missing_tunnel_url", path: "tunnel", message: "missing tunnel_url" });
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
const channelId = checkedGrant.channel_id.trim();
|
|
88
|
+
if (channelId === "") {
|
|
87
89
|
throw new FlowersecError({ stage: "validate", code: "missing_channel_id", path: "tunnel", message: "missing channel_id" });
|
|
88
90
|
}
|
|
89
|
-
|
|
91
|
+
const token = checkedGrant.token.trim();
|
|
92
|
+
if (token === "") {
|
|
90
93
|
throw new FlowersecError({ stage: "validate", code: "missing_token", path: "tunnel", message: "missing token" });
|
|
91
94
|
}
|
|
92
95
|
if (checkedGrant.channel_init_expire_at_unix_s <= 0) {
|
|
@@ -97,8 +100,9 @@ export async function connectTunnel(grant, opts) {
|
|
|
97
100
|
message: "missing channel_init_expire_at_unix_s",
|
|
98
101
|
});
|
|
99
102
|
}
|
|
103
|
+
const e2eePskB64u = checkedGrant.e2ee_psk_b64u.trim();
|
|
100
104
|
try {
|
|
101
|
-
const psk = base64urlDecode(
|
|
105
|
+
const psk = base64urlDecode(e2eePskB64u);
|
|
102
106
|
if (psk.length !== 32) {
|
|
103
107
|
throw new Error("psk must be 32 bytes");
|
|
104
108
|
}
|
|
@@ -134,18 +138,18 @@ export async function connectTunnel(grant, opts) {
|
|
|
134
138
|
}
|
|
135
139
|
const attach = {
|
|
136
140
|
v: 1,
|
|
137
|
-
channel_id:
|
|
141
|
+
channel_id: channelId,
|
|
138
142
|
role: TunnelRole.Role_client,
|
|
139
|
-
token
|
|
143
|
+
token,
|
|
140
144
|
endpoint_instance_id: endpointInstanceId
|
|
141
145
|
};
|
|
142
146
|
const attachJson = JSON.stringify(attach);
|
|
143
147
|
const keepaliveIntervalMs = opts.keepaliveIntervalMs ?? defaultKeepaliveIntervalMs(idleTimeoutSeconds);
|
|
144
148
|
return await connectCore({
|
|
145
149
|
path: "tunnel",
|
|
146
|
-
wsUrl:
|
|
147
|
-
channelId
|
|
148
|
-
e2eePskB64u
|
|
150
|
+
wsUrl: tunnelUrl,
|
|
151
|
+
channelId,
|
|
152
|
+
e2eePskB64u,
|
|
149
153
|
defaultSuite: checkedGrant.default_suite,
|
|
150
154
|
opts: { ...opts, keepaliveIntervalMs },
|
|
151
155
|
attach: { attachJson, endpointInstanceId }
|