@routr/connect 2.15.4 → 2.16.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.
package/dist/service.d.ts CHANGED
@@ -1,2 +1,17 @@
1
1
  import { ConnectProcessorConfig } from "./types";
2
+ import { MessageRequest } from "@routr/common";
3
+ import { Response } from "@routr/processor";
4
+ import { CommonConnect as CC } from "@routr/common";
5
+ import { ILocationService } from "@routr/location";
6
+ /**
7
+ * Builds the connect processor's message handler. Exported so the dispatch can be
8
+ * unit tested without a gRPC server.
9
+ *
10
+ * @param {object} deps - the API client and location service the handlers use
11
+ * @return {Function} the handler to pass to Processor#listen
12
+ */
13
+ export declare const createDispatcher: (deps: {
14
+ apiClient: CC.APIClient;
15
+ location: ILocationService;
16
+ }) => (req: MessageRequest, res: Response) => Promise<void>;
2
17
  export default function connectProcessor(config: ConnectProcessorConfig): void;
package/dist/service.js CHANGED
@@ -42,6 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
42
42
  });
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.createDispatcher = void 0;
45
46
  exports.default = connectProcessor;
46
47
  const common_1 = require("@routr/common");
47
48
  const location_1 = require("@routr/location");
@@ -52,50 +53,82 @@ const logger_1 = require("@fonoster/logger");
52
53
  const request_1 = require("./handlers/request");
53
54
  const registry_1 = require("./handlers/registry");
54
55
  const register_1 = require("./handlers/register");
56
+ const utils_1 = require("./utils");
55
57
  const logger = (0, logger_1.getLogger)({ service: "connect", filePath: __filename });
58
+ /**
59
+ * Forwards a request that belongs to an existing dialog along its route set: strip
60
+ * our own Route, add our Via, and let the EdgePort send it to the next hop or the
61
+ * Request-URI. No routing and no access checks, the dialog was authorized when it
62
+ * was set up. BYE and ACK always took this path; since RFC 3261 section 12.2 makes
63
+ * any request with a To tag an in-dialog request, re-INVITE, UPDATE, INFO, REFER and
64
+ * the NOTIFY that follows a REFER take it too.
65
+ *
66
+ * @param {MessageRequest} req - the in-dialog request
67
+ * @param {Response} res - the response object used to send it on
68
+ * @return {void}
69
+ */
70
+ const forwardInDialog = (req, res) => res.send((0, function_1.pipe)(req, processor_1.Alterations.decreaseMaxForwards, processor_1.Alterations.addSelfViaUsingExternalAddrs, processor_1.Alterations.removeSelfRoutes));
71
+ /**
72
+ * Builds the connect processor's message handler. Exported so the dispatch can be
73
+ * unit tested without a gRPC server.
74
+ *
75
+ * @param {object} deps - the API client and location service the handlers use
76
+ * @return {Function} the handler to pass to Processor#listen
77
+ */
78
+ const createDispatcher = (deps) => (req, res) => __awaiter(void 0, void 0, void 0, function* () {
79
+ const { apiClient, location } = deps;
80
+ logger.verbose("connect processor received new request", {
81
+ ref: req.ref,
82
+ method: req.method,
83
+ type: req.message.messageType === common_2.CommonTypes.MessageType.RESPONSE
84
+ ? "(response)"
85
+ : "(request)",
86
+ edgePort: req.edgePortRef
87
+ });
88
+ logger.silly(JSON.stringify(req, null, " "));
89
+ // If it is a response simply forwards to uac
90
+ if (processor_1.Helper.isTypeResponse(req)) {
91
+ // Remove the proxy and overwrite the contact with the sender info
92
+ return res.send(
93
+ // NOTE: We should consider making the overwriteContactWithSenderInfo an Agent/Peer level alteration
94
+ // pipe(req, A.overwriteContactWithSenderInfo, A.removeTopVia)
95
+ (0, function_1.pipe)(req, processor_1.Alterations.removeTopVia));
96
+ }
97
+ // REGISTER never carries a To tag and CANCEL keeps its own path (it follows
98
+ // the INVITE it cancels); everything else with a To tag is in a dialog.
99
+ if (req.method !== common_1.Method.REGISTER &&
100
+ req.method !== common_1.Method.CANCEL &&
101
+ (0, utils_1.isInDialog)(req)) {
102
+ return forwardInDialog(req, res);
103
+ }
104
+ switch (req.method) {
105
+ case common_1.Method.PUBLISH:
106
+ case common_1.Method.NOTIFY:
107
+ case common_1.Method.SUBSCRIBE:
108
+ case common_1.Method.MESSAGE:
109
+ res.sendMethodNotAllowed();
110
+ break;
111
+ case common_1.Method.REGISTER:
112
+ if (processor_1.Extensions.getHeaderValue(req, common_2.CommonTypes.ExtraHeader.GATEWAY_AUTH)) {
113
+ (0, registry_1.handleRegistry)(req, res);
114
+ }
115
+ else {
116
+ return (0, register_1.handleRegister)(apiClient, location)(req, res);
117
+ }
118
+ break;
119
+ case common_1.Method.BYE:
120
+ case common_1.Method.ACK:
121
+ forwardInDialog(req, res);
122
+ break;
123
+ default:
124
+ return (0, request_1.handleRequest)(location, apiClient)(req, res);
125
+ }
126
+ });
127
+ exports.createDispatcher = createDispatcher;
56
128
  // eslint-disable-next-line require-jsdoc
57
129
  function connectProcessor(config) {
58
130
  const { bindAddr, locationAddr } = config;
59
131
  const location = new location_1.LocationClient({ addr: locationAddr });
60
- new processor_1.default({ bindAddr, name: "connect" }).listen((req, res) => __awaiter(this, void 0, void 0, function* () {
61
- logger.verbose("connect processor received new request", {
62
- ref: req.ref,
63
- method: req.method,
64
- type: req.message.messageType === common_2.CommonTypes.MessageType.RESPONSE
65
- ? "(response)"
66
- : "(request)",
67
- edgePort: req.edgePortRef
68
- });
69
- logger.silly(JSON.stringify(req, null, " "));
70
- // If it is a response simply forwards to uac
71
- if (processor_1.Helper.isTypeResponse(req)) {
72
- // Remove the proxy and overwrite the contact with the sender info
73
- return res.send(
74
- // NOTE: We should consider making the overwriteContactWithSenderInfo an Agent/Peer level alteration
75
- // pipe(req, A.overwriteContactWithSenderInfo, A.removeTopVia)
76
- (0, function_1.pipe)(req, processor_1.Alterations.removeTopVia));
77
- }
78
- switch (req.method) {
79
- case common_1.Method.PUBLISH:
80
- case common_1.Method.NOTIFY:
81
- case common_1.Method.SUBSCRIBE:
82
- case common_1.Method.MESSAGE:
83
- res.sendMethodNotAllowed();
84
- break;
85
- case common_1.Method.REGISTER:
86
- if (processor_1.Extensions.getHeaderValue(req, common_2.CommonTypes.ExtraHeader.GATEWAY_AUTH)) {
87
- (0, registry_1.handleRegistry)(req, res);
88
- }
89
- else {
90
- (0, register_1.handleRegister)(common_2.CommonConnect.apiClient({ apiAddr: config.apiAddr }), location)(req, res);
91
- }
92
- break;
93
- case common_1.Method.BYE:
94
- case common_1.Method.ACK:
95
- res.send((0, function_1.pipe)(req, processor_1.Alterations.decreaseMaxForwards, processor_1.Alterations.addSelfViaUsingExternalAddrs, processor_1.Alterations.removeSelfRoutes));
96
- break;
97
- default:
98
- (0, request_1.handleRequest)(location, common_2.CommonConnect.apiClient({ apiAddr: config.apiAddr }))(req, res);
99
- }
100
- }));
132
+ const apiClient = common_2.CommonConnect.apiClient({ apiAddr: config.apiAddr });
133
+ new processor_1.default({ bindAddr, name: "connect" }).listen((0, exports.createDispatcher)({ apiClient, location }));
101
134
  }
package/dist/utils.d.ts CHANGED
@@ -18,6 +18,19 @@ export declare const getSipUri: (uri: {
18
18
  user?: string;
19
19
  host: string;
20
20
  }) => string;
21
+ /**
22
+ * Whether a request belongs to an existing dialog: RFC 3261 section 12.2, a request
23
+ * inside a dialog carries the To tag the dialog was established with. Such a request is
24
+ * forwarded along the dialog's route set and is never routed as a new call, which
25
+ * would send a hold or transfer re-INVITE to the PSTN trunk and answer the provider's
26
+ * re-INVITE with 500 (Routr v1 fixed the same bug in issue #58). The EdgePort refuses
27
+ * an in-dialog INVITE or REFER for a dialog it did not proxy, so forwarding here does
28
+ * not bypass access checks for a forged To tag.
29
+ *
30
+ * @param {MessageRequest} req - The request
31
+ * @return {boolean} true when the To header carries a tag
32
+ */
33
+ export declare const isInDialog: (req: MessageRequest) => boolean;
21
34
  export declare const hasXConnectObjectHeader: (req: MessageRequest) => string;
22
35
  export declare const getVerifierImpl: () => {
23
36
  verify: (token: string) => Promise<import("@routr/common/dist/errors").ServiceUnavailableError | V.VerifyResponse>;
package/dist/utils.js CHANGED
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.isFromTrustedEdgePort = exports.getSourceAddress = exports.getUntrustedHeadersToStrip = exports.getVerifierImpl = exports.hasXConnectObjectHeader = exports.getSipUri = exports.getTrunkURI = exports.createTrunkAuthentication = exports.createRemotePartyId = exports.createPAssertedIdentity = exports.getRoutingDirection = exports.findResource = exports.findNumberByTelUrl = exports.findDomain = exports.isKind = void 0;
48
+ exports.isFromTrustedEdgePort = exports.getSourceAddress = exports.getUntrustedHeadersToStrip = exports.getVerifierImpl = exports.hasXConnectObjectHeader = exports.isInDialog = exports.getSipUri = exports.getTrunkURI = exports.createTrunkAuthentication = exports.createRemotePartyId = exports.createPAssertedIdentity = exports.getRoutingDirection = exports.findResource = exports.findNumberByTelUrl = exports.findDomain = exports.isKind = void 0;
49
49
  /*
50
50
  * Copyright (C) 2026 by Fonoster Inc (https://fonoster.com)
51
51
  * http://github.com/fonoster/routr
@@ -206,6 +206,20 @@ const getTrunkURI = (trunk) => {
206
206
  exports.getTrunkURI = getTrunkURI;
207
207
  const getSipUri = (uri) => uri.user ? `sip:${uri.user}@${uri.host}` : `sip:${uri.host}`;
208
208
  exports.getSipUri = getSipUri;
209
+ /**
210
+ * Whether a request belongs to an existing dialog: RFC 3261 section 12.2, a request
211
+ * inside a dialog carries the To tag the dialog was established with. Such a request is
212
+ * forwarded along the dialog's route set and is never routed as a new call, which
213
+ * would send a hold or transfer re-INVITE to the PSTN trunk and answer the provider's
214
+ * re-INVITE with 500 (Routr v1 fixed the same bug in issue #58). The EdgePort refuses
215
+ * an in-dialog INVITE or REFER for a dialog it did not proxy, so forwarding here does
216
+ * not bypass access checks for a forged To tag.
217
+ *
218
+ * @param {MessageRequest} req - The request
219
+ * @return {boolean} true when the To header carries a tag
220
+ */
221
+ const isInDialog = (req) => { var _a; return !!((_a = req.message.to) === null || _a === void 0 ? void 0 : _a.tag); };
222
+ exports.isInDialog = isInDialog;
209
223
  const hasXConnectObjectHeader = (req) => processor_1.Extensions.getHeaderValue(req, common_1.CommonTypes.ExtraHeader.CONNECT_TOKEN);
210
224
  exports.hasXConnectObjectHeader = hasXConnectObjectHeader;
211
225
  const getVerifierImpl = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routr/connect",
3
- "version": "2.15.4",
3
+ "version": "2.16.0",
4
4
  "description": "Default processor",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/routr#readme",
@@ -20,9 +20,9 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@fonoster/logger": "0.6.0",
23
- "@routr/common": "^2.15.4",
24
- "@routr/location": "^2.15.4",
25
- "@routr/processor": "^2.15.4",
23
+ "@routr/common": "^2.16.0",
24
+ "@routr/location": "^2.16.0",
25
+ "@routr/processor": "^2.16.0",
26
26
  "jsonwebtoken": "^9.0.3"
27
27
  },
28
28
  "devDependencies": {
@@ -41,5 +41,5 @@
41
41
  "bugs": {
42
42
  "url": "https://github.com/fonoster/routr/issues"
43
43
  },
44
- "gitHead": "3d4a72191d5ac315429570e562d20fb5807cc942"
44
+ "gitHead": "db8a2eeb4b3f57a7f6b632d8d3378ffe53fc6cfe"
45
45
  }