@tiflis-io/tiflis-code-tunnel 0.3.2 → 0.3.4

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.
Files changed (2) hide show
  1. package/dist/main.js +185 -16
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -372,14 +372,16 @@ function registerWatchApiRoute(app, deps) {
372
372
  });
373
373
  app.post("/api/v1/watch/command", async (request, reply) => {
374
374
  try {
375
- const { device_id, message } = request.body;
376
- if (!device_id) {
375
+ const { tunnel_id, auth_key, device_id, message } = request.body;
376
+ if (!tunnel_id || !auth_key || !device_id) {
377
377
  return await reply.status(400).send({
378
378
  error: "missing_parameters",
379
- message: "device_id and message are required"
379
+ message: "tunnel_id, auth_key, device_id, and message are required"
380
380
  });
381
381
  }
382
- const sent = httpClientOperations.sendCommand({
382
+ const sent = httpClientOperations.sendCommandWithAuth({
383
+ tunnelId: tunnel_id,
384
+ authKey: auth_key,
383
385
  deviceId: device_id,
384
386
  message
385
387
  });
@@ -398,16 +400,18 @@ function registerWatchApiRoute(app, deps) {
398
400
  });
399
401
  app.get("/api/v1/watch/messages", async (request, reply) => {
400
402
  try {
401
- const { device_id, since, ack } = request.query;
402
- if (!device_id) {
403
+ const { tunnel_id, auth_key, device_id, since, ack } = request.query;
404
+ if (!tunnel_id || !auth_key || !device_id) {
403
405
  return await reply.status(400).send({
404
406
  error: "missing_parameters",
405
- message: "device_id is required"
407
+ message: "tunnel_id, auth_key, and device_id are required"
406
408
  });
407
409
  }
408
410
  const sinceSequence = since ? parseInt(since, 10) : 0;
409
411
  const ackSequence = ack ? parseInt(ack, 10) : void 0;
410
- const result = httpClientOperations.pollMessages({
412
+ const result = httpClientOperations.pollMessagesWithAuth({
413
+ tunnelId: tunnel_id,
414
+ authKey: auth_key,
411
415
  deviceId: device_id,
412
416
  sinceSequence,
413
417
  acknowledgeSequence: ackSequence
@@ -428,14 +432,16 @@ function registerWatchApiRoute(app, deps) {
428
432
  });
429
433
  app.get("/api/v1/watch/state", async (request, reply) => {
430
434
  try {
431
- const { device_id } = request.query;
432
- if (!device_id) {
435
+ const { tunnel_id, auth_key, device_id } = request.query;
436
+ if (!tunnel_id || !auth_key || !device_id) {
433
437
  return await reply.status(400).send({
434
438
  error: "missing_parameters",
435
- message: "device_id is required"
439
+ message: "tunnel_id, auth_key, and device_id are required"
436
440
  });
437
441
  }
438
- const result = httpClientOperations.getState({
442
+ const result = httpClientOperations.getStateWithAuth({
443
+ tunnelId: tunnel_id,
444
+ authKey: auth_key,
439
445
  deviceId: device_id
440
446
  });
441
447
  return await reply.status(200).send({
@@ -451,14 +457,18 @@ function registerWatchApiRoute(app, deps) {
451
457
  });
452
458
  app.post("/api/v1/watch/disconnect", async (request, reply) => {
453
459
  try {
454
- const { device_id } = request.body;
455
- if (!device_id) {
460
+ const { tunnel_id, auth_key, device_id } = request.body;
461
+ if (!tunnel_id || !auth_key || !device_id) {
456
462
  return await reply.status(400).send({
457
463
  error: "missing_parameters",
458
- message: "device_id is required"
464
+ message: "tunnel_id, auth_key, and device_id are required"
459
465
  });
460
466
  }
461
- const disconnected = httpClientOperations.disconnect(device_id);
467
+ const disconnected = httpClientOperations.disconnectWithAuth({
468
+ tunnelId: tunnel_id,
469
+ authKey: auth_key,
470
+ deviceId: device_id
471
+ });
462
472
  log.info({ deviceId: device_id }, "Watch disconnected via HTTP");
463
473
  return await reply.status(200).send({
464
474
  success: disconnected
@@ -1839,6 +1849,7 @@ var HttpClientOperationsUseCase = class {
1839
1849
  throw new InvalidAuthKeyError();
1840
1850
  }
1841
1851
  let client = this.httpClientRegistry.get(deviceId);
1852
+ const isNewClient = !client;
1842
1853
  if (client) {
1843
1854
  client.recordPoll();
1844
1855
  this.logger.info({ deviceId, tunnelId: tunnelIdStr }, "HTTP client reconnected");
@@ -1850,6 +1861,21 @@ var HttpClientOperationsUseCase = class {
1850
1861
  this.httpClientRegistry.register(client);
1851
1862
  this.logger.info({ deviceId, tunnelId: tunnelIdStr }, "HTTP client registered");
1852
1863
  }
1864
+ if (workstation.isOnline) {
1865
+ const authMessage = {
1866
+ type: "auth",
1867
+ payload: {
1868
+ auth_key: authKeyStr,
1869
+ device_id: deviceId
1870
+ }
1871
+ };
1872
+ const sent = workstation.send(JSON.stringify(authMessage));
1873
+ if (sent) {
1874
+ this.logger.debug({ deviceId, isNewClient }, "Forwarded auth to workstation for HTTP client");
1875
+ } else {
1876
+ this.logger.warn({ deviceId }, "Failed to forward auth to workstation");
1877
+ }
1878
+ }
1853
1879
  return {
1854
1880
  success: true,
1855
1881
  tunnelId: tunnelIdStr,
@@ -1888,6 +1914,57 @@ var HttpClientOperationsUseCase = class {
1888
1914
  }
1889
1915
  return sent;
1890
1916
  }
1917
+ /**
1918
+ * Sends a command from HTTP client to workstation with auth validation.
1919
+ * This method validates auth on every request and forwards auth to workstation
1920
+ * to ensure the device_id is registered.
1921
+ */
1922
+ sendCommandWithAuth(input) {
1923
+ const { tunnelId: tunnelIdStr, authKey: authKeyStr, deviceId, message } = input;
1924
+ this.logger.info({ deviceId, tunnelId: tunnelIdStr, messageType: message.type }, "HTTP client sending command with auth");
1925
+ const tunnelId = TunnelId.create(tunnelIdStr);
1926
+ const authKey = AuthKey.create(authKeyStr);
1927
+ const workstation = this.workstationRegistry.get(tunnelId);
1928
+ if (!workstation) {
1929
+ this.logger.warn({ tunnelId: tunnelIdStr, deviceId }, "Workstation not found for command");
1930
+ throw new TunnelNotFoundError(tunnelIdStr);
1931
+ }
1932
+ if (!workstation.validateAuthKey(authKey)) {
1933
+ this.logger.warn({ tunnelId: tunnelIdStr, deviceId }, "Invalid auth key for command");
1934
+ throw new InvalidAuthKeyError();
1935
+ }
1936
+ if (!workstation.isOnline) {
1937
+ this.logger.warn({ deviceId, tunnelId: tunnelIdStr }, "Workstation offline");
1938
+ throw new WorkstationOfflineError(tunnelIdStr);
1939
+ }
1940
+ let client = this.httpClientRegistry.get(deviceId);
1941
+ if (!client) {
1942
+ client = new HttpClient({
1943
+ deviceId,
1944
+ tunnelId
1945
+ });
1946
+ this.httpClientRegistry.register(client);
1947
+ this.logger.info({ deviceId, tunnelId: tunnelIdStr }, "HTTP client registered on command");
1948
+ }
1949
+ client.recordPoll();
1950
+ const authMessage = {
1951
+ type: "auth",
1952
+ payload: {
1953
+ auth_key: authKeyStr,
1954
+ device_id: deviceId
1955
+ }
1956
+ };
1957
+ workstation.send(JSON.stringify(authMessage));
1958
+ const enrichedMessage = {
1959
+ ...message,
1960
+ device_id: deviceId
1961
+ };
1962
+ const sent = workstation.send(JSON.stringify(enrichedMessage));
1963
+ if (!sent) {
1964
+ this.logger.warn({ deviceId }, "Failed to send command to workstation");
1965
+ }
1966
+ return sent;
1967
+ }
1891
1968
  /**
1892
1969
  * Polls for messages for an HTTP client.
1893
1970
  */
@@ -1956,6 +2033,98 @@ var HttpClientOperationsUseCase = class {
1956
2033
  this.logger.info({ deviceId }, "HTTP client disconnected");
1957
2034
  return true;
1958
2035
  }
2036
+ /**
2037
+ * Polls for messages with auth validation (stateless).
2038
+ * Validates auth on every request.
2039
+ */
2040
+ pollMessagesWithAuth(input) {
2041
+ const { tunnelId: tunnelIdStr, authKey: authKeyStr, deviceId, sinceSequence, acknowledgeSequence } = input;
2042
+ const tunnelId = TunnelId.create(tunnelIdStr);
2043
+ const authKey = AuthKey.create(authKeyStr);
2044
+ const workstation = this.workstationRegistry.get(tunnelId);
2045
+ if (!workstation) {
2046
+ throw new TunnelNotFoundError(tunnelIdStr);
2047
+ }
2048
+ if (!workstation.validateAuthKey(authKey)) {
2049
+ throw new InvalidAuthKeyError();
2050
+ }
2051
+ let client = this.httpClientRegistry.get(deviceId);
2052
+ if (!client) {
2053
+ client = new HttpClient({
2054
+ deviceId,
2055
+ tunnelId
2056
+ });
2057
+ this.httpClientRegistry.register(client);
2058
+ }
2059
+ client.recordPoll();
2060
+ if (acknowledgeSequence !== void 0 && acknowledgeSequence > 0) {
2061
+ client.acknowledgeMessages(acknowledgeSequence);
2062
+ }
2063
+ const messages = client.getMessagesSince(sinceSequence);
2064
+ this.logger.debug(
2065
+ { deviceId, sinceSequence, messageCount: messages.length, currentSequence: client.currentSequence },
2066
+ "HTTP client poll with auth"
2067
+ );
2068
+ return {
2069
+ messages,
2070
+ currentSequence: client.currentSequence,
2071
+ workstationOnline: workstation.isOnline
2072
+ };
2073
+ }
2074
+ /**
2075
+ * Gets current state with auth validation (stateless).
2076
+ */
2077
+ getStateWithAuth(input) {
2078
+ const { tunnelId: tunnelIdStr, authKey: authKeyStr, deviceId } = input;
2079
+ const tunnelId = TunnelId.create(tunnelIdStr);
2080
+ const authKey = AuthKey.create(authKeyStr);
2081
+ const workstation = this.workstationRegistry.get(tunnelId);
2082
+ if (!workstation) {
2083
+ throw new TunnelNotFoundError(tunnelIdStr);
2084
+ }
2085
+ if (!workstation.validateAuthKey(authKey)) {
2086
+ throw new InvalidAuthKeyError();
2087
+ }
2088
+ let client = this.httpClientRegistry.get(deviceId);
2089
+ if (!client) {
2090
+ client = new HttpClient({
2091
+ deviceId,
2092
+ tunnelId
2093
+ });
2094
+ this.httpClientRegistry.register(client);
2095
+ }
2096
+ client.recordPoll();
2097
+ return {
2098
+ connected: true,
2099
+ workstationOnline: workstation.isOnline,
2100
+ workstationName: workstation.name,
2101
+ queueSize: client.queueSize,
2102
+ currentSequence: client.currentSequence
2103
+ };
2104
+ }
2105
+ /**
2106
+ * Disconnects an HTTP client with auth validation (stateless).
2107
+ */
2108
+ disconnectWithAuth(input) {
2109
+ const { tunnelId: tunnelIdStr, authKey: authKeyStr, deviceId } = input;
2110
+ const tunnelId = TunnelId.create(tunnelIdStr);
2111
+ const authKey = AuthKey.create(authKeyStr);
2112
+ const workstation = this.workstationRegistry.get(tunnelId);
2113
+ if (!workstation) {
2114
+ throw new TunnelNotFoundError(tunnelIdStr);
2115
+ }
2116
+ if (!workstation.validateAuthKey(authKey)) {
2117
+ throw new InvalidAuthKeyError();
2118
+ }
2119
+ const client = this.httpClientRegistry.get(deviceId);
2120
+ if (!client) {
2121
+ return false;
2122
+ }
2123
+ client.markInactive();
2124
+ this.httpClientRegistry.unregister(deviceId);
2125
+ this.logger.info({ deviceId }, "HTTP client disconnected with auth");
2126
+ return true;
2127
+ }
1959
2128
  /**
1960
2129
  * Queues a message for all HTTP clients connected to a tunnel.
1961
2130
  * Called when workstation sends a message.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-tunnel",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Tunnel server for tiflis-code - reverse proxy for workstation connections",
5
5
  "author": "Roman Barinov <rbarinov@gmail.com>",
6
6
  "license": "FSL-1.1-NC",