Skip to content

Commit 022581d

Browse files
849261680vincentkoc
authored andcommitted
fix(gateway): clarify websocket handshake phases
1 parent cddf243 commit 022581d

4 files changed

Lines changed: 54 additions & 7 deletions

File tree

src/gateway/server/ws-connection.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ describe("attachGatewayWsConnectionHandler", () => {
262262
).toBe(true);
263263
handlerParams.setHandshakeState("connected");
264264
handlerParams.advanceHandshakePhase("session_attached");
265-
handlerParams.advanceHandshakePhase("subscriptions_registered");
265+
handlerParams.advanceHandshakePhase("hello_payload_prepared");
266266
handlerParams.advanceHandshakePhase("ready");
267267

268268
socket.emit("close", 1000, Buffer.from("done"));

src/gateway/server/ws-connection/message-handler.post-connect-health.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ function attachGatewayHarness(options: {
212212
mode: "none",
213213
allowTailscale: false,
214214
};
215+
const advanceHandshakePhase = vi.fn();
215216
attachGatewayWsMessageHandler({
216217
socket,
217218
upgradeReq: {
@@ -244,7 +245,7 @@ function attachGatewayHarness(options: {
244245
return true;
245246
},
246247
setHandshakeState: vi.fn(),
247-
advanceHandshakePhase: vi.fn(),
248+
advanceHandshakePhase,
248249
setCloseCause: options.setCloseCause ?? createSetCloseCauseMock(),
249250
setLastFrameMeta: vi.fn(),
250251
originCheckMetrics: { hostHeaderFallbackAccepted: 0 },
@@ -257,6 +258,7 @@ function attachGatewayHarness(options: {
257258
}
258259
const sendMessage = onMessage;
259260
return {
261+
advanceHandshakePhase,
260262
socketSend,
261263
sendRequest: (id: string, method: string, params: Record<string, unknown> = {}) => {
262264
sendMessage(
@@ -585,6 +587,46 @@ describe("attachGatewayWsMessageHandler post-connect health refresh", () => {
585587
expect(JSON.stringify(captured.events)).not.toContain("gateway-token");
586588
});
587589

590+
it("records credential and hello preparation phases during connect", async () => {
591+
const harness = attachGatewayHarness({
592+
connId: "conn-phases",
593+
connectNonce: "nonce-phases",
594+
resolvedAuth: {
595+
mode: "token",
596+
token: "gateway-token",
597+
allowTailscale: false,
598+
},
599+
});
600+
601+
harness.sendConnect("connect-phases", {
602+
minProtocol: PROTOCOL_VERSION,
603+
maxProtocol: PROTOCOL_VERSION,
604+
client: {
605+
id: "gateway-client",
606+
version: "dev",
607+
platform: "test",
608+
mode: "backend",
609+
},
610+
role: "operator",
611+
scopes: [],
612+
caps: [],
613+
auth: {
614+
token: "gateway-token",
615+
},
616+
});
617+
618+
await vi.waitFor(() => {
619+
expect(harness.socketSend).toHaveBeenCalled();
620+
});
621+
expect(harness.advanceHandshakePhase.mock.calls.map(([phase]) => phase)).toEqual([
622+
"auth_credentials_received",
623+
"auth_validated",
624+
"session_attached",
625+
"hello_payload_prepared",
626+
"ready",
627+
]);
628+
});
629+
588630
it("does not mark local backend self-pairing clients as approval runtimes", async () => {
589631
const refreshHealthSnapshot = vi.fn<GatewayRequestContext["refreshHealthSnapshot"]>(async () =>
590632
createHealthSummary(),

src/gateway/server/ws-connection/message-handler.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,14 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
930930
deviceRaw,
931931
});
932932
const device = controlUiAuthPolicy.device;
933+
const hasRawHandshakeCredentials =
934+
hasSharedAuth ||
935+
Boolean(connectParams.auth?.bootstrapToken) ||
936+
Boolean(connectParams.auth?.deviceToken) ||
937+
Boolean(device);
938+
if (hasRawHandshakeCredentials) {
939+
advanceHandshakePhase("auth_credentials_received");
940+
}
933941
const connectAuthState = await resolveConnectAuthState({
934942
resolvedAuth,
935943
connectAuth: connectParams.auth,
@@ -946,9 +954,6 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
946954
deviceTokenCandidate,
947955
deviceTokenCandidateSource,
948956
} = connectAuthState;
949-
if (hasSharedAuth || bootstrapTokenCandidate || deviceTokenCandidate || device) {
950-
advanceHandshakePhase("auth_credentials_received");
951-
}
952957
let { authResult, authOk, authMethod } = connectAuthState;
953958
const rejectUnauthorized = (failedAuth: GatewayAuthResult) => {
954959
const { authProvided, canRetryWithDeviceToken, recommendedNextStep } =
@@ -2153,7 +2158,6 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
21532158
);
21542159
}
21552160

2156-
advanceHandshakePhase("subscriptions_registered");
21572161
const snapshot = buildGatewaySnapshot({
21582162
includeSensitive: scopes.includes(ADMIN_SCOPE),
21592163
});
@@ -2198,6 +2202,7 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
21982202
tickIntervalMs: TICK_INTERVAL_MS,
21992203
},
22002204
};
2205+
advanceHandshakePhase("hello_payload_prepared");
22012206

22022207
let revokedBootstrapTokenRecord:
22032208
| Awaited<ReturnType<typeof revokeDeviceBootstrapToken>>["record"]

src/gateway/server/ws-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const WS_HANDSHAKE_PHASES = [
3333
"auth_credentials_received",
3434
"auth_validated",
3535
"session_attached",
36-
"subscriptions_registered",
36+
"hello_payload_prepared",
3737
"ready",
3838
] as const;
3939

0 commit comments

Comments
 (0)