154 lines
5.4 KiB
JavaScript
154 lines
5.4 KiB
JavaScript
|
|
const test = require("node:test");
|
||
|
|
const assert = require("node:assert/strict");
|
||
|
|
|
||
|
|
const keyboardInteractiveHandler = require("./keyboardInteractiveHandler.cjs");
|
||
|
|
|
||
|
|
test("keyboard-interactive responses from the wrong renderer are rejected and kept pending", () => {
|
||
|
|
const finishCalls = [];
|
||
|
|
const requestId = keyboardInteractiveHandler.generateRequestId("test");
|
||
|
|
keyboardInteractiveHandler.storeRequest(requestId, (responses) => {
|
||
|
|
finishCalls.push(responses);
|
||
|
|
}, 101, "session-1");
|
||
|
|
|
||
|
|
const wrongResult = keyboardInteractiveHandler.handleResponse(
|
||
|
|
{ sender: { id: 202 } },
|
||
|
|
{ requestId, responses: ["wrong"] },
|
||
|
|
);
|
||
|
|
assert.deepEqual(wrongResult, { success: false, error: "Wrong sender" });
|
||
|
|
assert.deepEqual(finishCalls, []);
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(requestId), true);
|
||
|
|
|
||
|
|
const correctResult = keyboardInteractiveHandler.handleResponse(
|
||
|
|
{ sender: { id: 101 } },
|
||
|
|
{ requestId, responses: ["correct"] },
|
||
|
|
);
|
||
|
|
assert.deepEqual(correctResult, { success: true });
|
||
|
|
assert.deepEqual(finishCalls, [["correct"]]);
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(requestId), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("keyboard-interactive responses never write password or OTP contents to logs", (t) => {
|
||
|
|
const logs = [];
|
||
|
|
t.mock.method(console, "log", (...args) => logs.push(args));
|
||
|
|
const finishCalls = [];
|
||
|
|
const requestId = keyboardInteractiveHandler.generateRequestId("secret-log-test");
|
||
|
|
const secrets = ["secondary-password-secret", "123456-otp-secret"];
|
||
|
|
|
||
|
|
keyboardInteractiveHandler.storeRequest(requestId, (responses) => {
|
||
|
|
finishCalls.push(responses);
|
||
|
|
}, 303, "session-secret-log-test");
|
||
|
|
|
||
|
|
const result = keyboardInteractiveHandler.handleResponse(
|
||
|
|
{ sender: { id: 303 } },
|
||
|
|
{ requestId, responses: secrets, cancelled: false },
|
||
|
|
);
|
||
|
|
|
||
|
|
assert.deepEqual(result, { success: true });
|
||
|
|
assert.deepEqual(finishCalls, [secrets]);
|
||
|
|
const serializedLogs = JSON.stringify(logs);
|
||
|
|
for (const secret of secrets) {
|
||
|
|
assert.equal(serializedLogs.includes(secret), false);
|
||
|
|
}
|
||
|
|
assert.equal(
|
||
|
|
logs.some((entry) => entry.some((value) => value?.responsesCount === secrets.length)),
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("keyboard-interactive delivery failures close the renderer prompt", (t) => {
|
||
|
|
t.mock.method(console, "warn", () => {});
|
||
|
|
const notifications = [];
|
||
|
|
const requestId = keyboardInteractiveHandler.generateRequestId("delivery-failure");
|
||
|
|
keyboardInteractiveHandler.storeRequest(
|
||
|
|
requestId,
|
||
|
|
() => {
|
||
|
|
throw new Error("connection closed");
|
||
|
|
},
|
||
|
|
404,
|
||
|
|
"session-delivery-failure",
|
||
|
|
{
|
||
|
|
isDestroyed: () => false,
|
||
|
|
send: (channel, payload) => notifications.push({ channel, payload }),
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const result = keyboardInteractiveHandler.handleResponse(
|
||
|
|
{ sender: { id: 404 } },
|
||
|
|
{ requestId, responses: ["123456"], cancelled: false },
|
||
|
|
);
|
||
|
|
|
||
|
|
assert.deepEqual(result, { success: false, error: "Failed to deliver response" });
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(requestId), false);
|
||
|
|
assert.deepEqual(notifications, [{
|
||
|
|
channel: "netcatty:keyboard-interactive-cancelled",
|
||
|
|
payload: {
|
||
|
|
requestId,
|
||
|
|
sessionId: "session-delivery-failure",
|
||
|
|
reason: "delivery-failed",
|
||
|
|
},
|
||
|
|
}]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("keyboard-interactive responses settle once when delivery closes the session", () => {
|
||
|
|
const finishCalls = [];
|
||
|
|
const requestId = keyboardInteractiveHandler.generateRequestId("reentrant-close");
|
||
|
|
keyboardInteractiveHandler.storeRequest(
|
||
|
|
requestId,
|
||
|
|
(responses) => {
|
||
|
|
finishCalls.push(responses);
|
||
|
|
keyboardInteractiveHandler.cancelRequestsForSession("session-reentrant-close");
|
||
|
|
},
|
||
|
|
505,
|
||
|
|
"session-reentrant-close",
|
||
|
|
);
|
||
|
|
|
||
|
|
const result = keyboardInteractiveHandler.handleResponse(
|
||
|
|
{ sender: { id: 505 } },
|
||
|
|
{ requestId, responses: ["123456"], cancelled: false },
|
||
|
|
);
|
||
|
|
|
||
|
|
assert.deepEqual(result, { success: true });
|
||
|
|
assert.deepEqual(finishCalls, [["123456"]]);
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(requestId), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("keyboard-interactive requests can be cancelled by owning session", () => {
|
||
|
|
const finishCalls = [];
|
||
|
|
const notifications = [];
|
||
|
|
const matchingRequestId = keyboardInteractiveHandler.generateRequestId("matching");
|
||
|
|
const otherRequestId = keyboardInteractiveHandler.generateRequestId("other");
|
||
|
|
keyboardInteractiveHandler.storeRequest(
|
||
|
|
matchingRequestId,
|
||
|
|
(responses) => finishCalls.push({ requestId: matchingRequestId, responses }),
|
||
|
|
101,
|
||
|
|
"tunnel-1",
|
||
|
|
{
|
||
|
|
isDestroyed: () => false,
|
||
|
|
send: (channel, payload) => notifications.push({ channel, payload }),
|
||
|
|
},
|
||
|
|
);
|
||
|
|
keyboardInteractiveHandler.storeRequest(
|
||
|
|
otherRequestId,
|
||
|
|
(responses) => finishCalls.push({ requestId: otherRequestId, responses }),
|
||
|
|
101,
|
||
|
|
"tunnel-2",
|
||
|
|
);
|
||
|
|
|
||
|
|
const cancelled = keyboardInteractiveHandler.cancelRequestsForSession("tunnel-1", "tunnel-stopped");
|
||
|
|
|
||
|
|
assert.equal(cancelled, 1);
|
||
|
|
assert.deepEqual(finishCalls, [{ requestId: matchingRequestId, responses: [] }]);
|
||
|
|
assert.deepEqual(notifications, [{
|
||
|
|
channel: "netcatty:keyboard-interactive-cancelled",
|
||
|
|
payload: {
|
||
|
|
requestId: matchingRequestId,
|
||
|
|
sessionId: "tunnel-1",
|
||
|
|
reason: "tunnel-stopped",
|
||
|
|
},
|
||
|
|
}]);
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(matchingRequestId), false);
|
||
|
|
assert.equal(keyboardInteractiveHandler.getRequests().has(otherRequestId), true);
|
||
|
|
|
||
|
|
keyboardInteractiveHandler.cancelRequestsForSession("tunnel-2");
|
||
|
|
});
|