@serve.zone/dcrouter 32.0.0 → 32.0.1

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.
@@ -11,6 +11,12 @@ import {
11
11
  normalizeGatewayTargetHost,
12
12
  requireGatewayMachineAuth,
13
13
  } from '../helpers/gateway-client-auth.js';
14
+ import {
15
+ buildGatewayClientHostRouteKey,
16
+ buildGatewayClientRouteName,
17
+ parseGatewayClientHostRouteKey,
18
+ } from '../helpers/gateway-client-route-key.js';
19
+ import { logger } from '../../logger.js';
14
20
 
15
21
  type TResolvedGatewayClientOwnership = Required<Pick<plugins.servezoneInterfaces.data.IGatewayClientOwnership,
16
22
  'gatewayClientType' | 'gatewayClientId' | 'appId'
@@ -1171,15 +1177,22 @@ export class GatewayClientHandler {
1171
1177
  const routeKey = metadata.externalKey?.startsWith(prefix)
1172
1178
  ? metadata.externalKey.slice(prefix.length)
1173
1179
  : '';
1174
- const combinedOwnership = this.parseCombinedGatewayClientRouteKey(routeKey);
1180
+ const combinedOwnership = parseGatewayClientHostRouteKey(routeKey);
1175
1181
  if (combinedOwnership) {
1176
1182
  ownership.hostname = combinedOwnership.hostname;
1177
1183
  ownership.routeRef = combinedOwnership.routeRef;
1178
- } else if (routeKey.startsWith('v2:host-route:')) {
1179
- // A malformed versioned key must not be exposed as a hostname.
1180
- ownership.hostname = this.getRouteHostnames(routeArg.route)[0];
1181
1184
  } else if (routeKey.startsWith('route:')) {
1182
1185
  ownership.routeRef = routeKey.slice('route:'.length);
1186
+ } else if (routeKey.includes(':')) {
1187
+ // A hostname carries no colon, so a colon-bearing key that parses as neither of the two
1188
+ // other forms states ownership this router cannot prove. Report what the route actually
1189
+ // serves rather than the key, and say so instead of dropping the key silently.
1190
+ logger.log(
1191
+ 'warn',
1192
+ `Gateway client route ${routeArg.id} carries a malformed route key '${routeKey}'; listing it with its route hostname only.`,
1193
+ { routeId: routeArg.id },
1194
+ );
1195
+ ownership.hostname = this.getRouteHostnames(routeArg.route)[0];
1183
1196
  } else if (routeKey) {
1184
1197
  ownership.hostname = routeKey;
1185
1198
  } else {
@@ -1659,8 +1672,7 @@ export class GatewayClientHandler {
1659
1672
  const routeRef = ownership.routeRef?.trim();
1660
1673
  let routeKey: string;
1661
1674
  if (hostname && routeRef) {
1662
- const payload = Buffer.from(JSON.stringify([hostname, routeRef]), 'utf8').toString('base64url');
1663
- routeKey = `v2:host-route:${payload}`;
1675
+ routeKey = buildGatewayClientHostRouteKey(hostname, routeRef);
1664
1676
  } else if (hostname) {
1665
1677
  routeKey = hostname;
1666
1678
  } else {
@@ -1674,35 +1686,6 @@ export class GatewayClientHandler {
1674
1686
  ].map((part) => part.trim()).join(':');
1675
1687
  }
1676
1688
 
1677
- private parseCombinedGatewayClientRouteKey(
1678
- routeKeyArg: string,
1679
- ): { hostname: string; routeRef: string } | undefined {
1680
- const prefix = 'v2:host-route:';
1681
- if (!routeKeyArg.startsWith(prefix)) {
1682
- return undefined;
1683
- }
1684
- const payload = routeKeyArg.slice(prefix.length);
1685
- if (!payload || !/^[A-Za-z0-9_-]+$/.test(payload)) {
1686
- return undefined;
1687
- }
1688
- try {
1689
- const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
1690
- if (
1691
- !Array.isArray(parsed)
1692
- || parsed.length !== 2
1693
- || parsed.some((value) => typeof value !== 'string' || !value.trim())
1694
- ) {
1695
- return undefined;
1696
- }
1697
- return {
1698
- hostname: parsed[0].trim().toLowerCase(),
1699
- routeRef: parsed[1].trim(),
1700
- };
1701
- } catch {
1702
- return undefined;
1703
- }
1704
- }
1705
-
1706
1689
  private validateGatewayRouteConfig(
1707
1690
  routeArg: unknown,
1708
1691
  ): plugins.servezoneInterfaces.data.IGatewayRouteConfig {
@@ -1882,7 +1865,7 @@ export class GatewayClientHandler {
1882
1865
  } as any;
1883
1866
  }
1884
1867
  if (!normalizedRoute.name) {
1885
- normalizedRoute.name = `gateway-client-${externalKey.replace(/[^a-zA-Z0-9-]+/g, '-').slice(0, 80)}`;
1868
+ normalizedRoute.name = buildGatewayClientRouteName(externalKey);
1886
1869
  }
1887
1870
  return normalizedRoute;
1888
1871
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The route-key half of a gateway-client route's `externalKey`, owned here so the composer,
3
+ * the parser, the generated route name and the migration that rewrote stored keys all read
4
+ * one spelling.
5
+ *
6
+ * A stored key is `<gatewayClientType>:<gatewayClientId>:<appId>:<routeKey>`, and the route key
7
+ * is one of three forms: a bare hostname, `route:<routeRef>` when the client owns a route
8
+ * reference without a hostname, and the combined form below when it owns one hostname under a
9
+ * specific route reference. The combined payload is base64url so the whole key stays one opaque
10
+ * segment, and neither other form can be mistaken for it: a hostname carries no colon, and the
11
+ * route-reference form has a different prefix.
12
+ */
13
+ export const gatewayClientHostRouteKeyPrefix = 'host-route:';
14
+
15
+ export interface IGatewayClientHostRouteKey {
16
+ hostname: string;
17
+ routeRef: string;
18
+ }
19
+
20
+ export function buildGatewayClientHostRouteKey(
21
+ hostnameArg: string,
22
+ routeRefArg: string,
23
+ ): string {
24
+ const payload = Buffer.from(JSON.stringify([hostnameArg, routeRefArg]), 'utf8').toString('base64url');
25
+ return `${gatewayClientHostRouteKeyPrefix}${payload}`;
26
+ }
27
+
28
+ /**
29
+ * Read a combined route key back. Returns undefined for every key that is not one — including a
30
+ * key that carries the prefix but no decodable payload, so a malformed key can never be served
31
+ * as ownership it does not state.
32
+ */
33
+ export function parseGatewayClientHostRouteKey(
34
+ routeKeyArg: string,
35
+ ): IGatewayClientHostRouteKey | undefined {
36
+ if (!routeKeyArg.startsWith(gatewayClientHostRouteKeyPrefix)) {
37
+ return undefined;
38
+ }
39
+ const payload = routeKeyArg.slice(gatewayClientHostRouteKeyPrefix.length);
40
+ if (!payload || !/^[A-Za-z0-9_-]+$/.test(payload)) {
41
+ return undefined;
42
+ }
43
+ try {
44
+ const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
45
+ if (
46
+ !Array.isArray(parsed)
47
+ || parsed.length !== 2
48
+ || parsed.some((value) => typeof value !== 'string' || !value.trim())
49
+ ) {
50
+ return undefined;
51
+ }
52
+ return {
53
+ hostname: parsed[0].trim().toLowerCase(),
54
+ routeRef: parsed[1].trim(),
55
+ };
56
+ } catch {
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ /** The route name generated for a gateway-client route whose producer sent none. */
62
+ export function buildGatewayClientRouteName(externalKeyArg: string): string {
63
+ return `gateway-client-${externalKeyArg.replace(/[^a-zA-Z0-9-]+/g, '-').slice(0, 80)}`;
64
+ }
@@ -2,7 +2,7 @@
2
2
  * autocreated commitinfo by @push.rocks/commitinfo
3
3
  */
4
4
  export const commitinfo = {
5
- name: "@serve.zone/dcrouter",
6
- version: "32.0.0",
7
- description: "A multifaceted routing service handling mail and SMS delivery functions."
5
+ name: '@serve.zone/dcrouter',
6
+ version: '32.0.1',
7
+ description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }