@serve.zone/dcrouter 13.41.1 → 13.42.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.
Files changed (31) hide show
  1. package/deno.json +3 -3
  2. package/dist_serve/bundle.js +1025 -983
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/config/classes.db-seeder.js +29 -2
  5. package/dist_ts/config/classes.reference-resolver.d.ts +5 -0
  6. package/dist_ts/config/classes.reference-resolver.js +79 -15
  7. package/dist_ts/config/classes.route-config-manager.d.ts +5 -1
  8. package/dist_ts/config/classes.route-config-manager.js +136 -6
  9. package/dist_ts/config/classes.source-policy-compiler.d.ts +35 -0
  10. package/dist_ts/config/classes.source-policy-compiler.js +497 -0
  11. package/dist_ts/config/index.d.ts +1 -0
  12. package/dist_ts/config/index.js +2 -1
  13. package/dist_ts_interfaces/data/route-management.d.ts +39 -0
  14. package/dist_ts_interfaces/data/route-management.js +65 -1
  15. package/dist_ts_migrations/index.js +67 -1
  16. package/dist_ts_web/00_commitinfo_data.js +1 -1
  17. package/dist_ts_web/elements/network/ops-view-routes.d.ts +2 -0
  18. package/dist_ts_web/elements/network/ops-view-routes.js +237 -11
  19. package/dist_ts_web/elements/network/ops-view-sourceprofiles.js +42 -5
  20. package/package.json +4 -4
  21. package/readme.md +74 -0
  22. package/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/config/classes.db-seeder.ts +28 -1
  24. package/ts/config/classes.reference-resolver.ts +94 -14
  25. package/ts/config/classes.route-config-manager.ts +162 -5
  26. package/ts/config/classes.source-policy-compiler.ts +614 -0
  27. package/ts/config/index.ts +1 -0
  28. package/ts/readme.md +1 -1
  29. package/ts_web/00_commitinfo_data.ts +1 -1
  30. package/ts_web/elements/network/ops-view-routes.ts +257 -10
  31. package/ts_web/elements/network/ops-view-sourceprofiles.ts +41 -4
@@ -37,6 +37,31 @@ import * as appstate from '../../appstate.js';
37
37
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
38
38
  import { viewHostCss } from '../shared/css.js';
39
39
  import {} from '@design.estate/dees-catalog';
40
+ function parseOptionalPositiveInteger(value) {
41
+ const parsed = typeof value === 'number'
42
+ ? value
43
+ : typeof value === 'string'
44
+ ? parseInt(value.trim(), 10)
45
+ : Number.NaN;
46
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
47
+ }
48
+ function buildRateLimitFromFormData(data) {
49
+ if (!Boolean(data.rateLimitEnabled)) {
50
+ return undefined;
51
+ }
52
+ const maxRequests = parseOptionalPositiveInteger(data.rateLimitMaxRequests);
53
+ const window = parseOptionalPositiveInteger(data.rateLimitWindow);
54
+ if (!maxRequests || !window) {
55
+ alert('Rate limit requires positive Max Requests and Window values.');
56
+ return null;
57
+ }
58
+ return {
59
+ enabled: true,
60
+ maxRequests,
61
+ window,
62
+ keyBy: 'ip',
63
+ };
64
+ }
40
65
  let OpsViewSourceProfiles = (() => {
41
66
  let _classDecorators = [customElement('ops-view-sourceprofiles')];
42
67
  let _classDescriptor;
@@ -169,6 +194,9 @@ let OpsViewSourceProfiles = (() => {
169
194
  <dees-input-list .key=${'ipAllowList'} .label=${'IP Allow List'} .placeholder=${'Add IP or CIDR...'}></dees-input-list>
170
195
  <dees-input-list .key=${'ipBlockList'} .label=${'IP Block List'} .placeholder=${'Add IP or CIDR...'}></dees-input-list>
171
196
  <dees-input-text .key=${'maxConnections'} .label=${'Max Connections'}></dees-input-text>
197
+ <dees-input-checkbox .key=${'rateLimitEnabled'} .label=${'Enable Rate Limit'} .description=${'Per source IP. Exceeded requests receive 429.'} .value=${false}></dees-input-checkbox>
198
+ <dees-input-text .key=${'rateLimitMaxRequests'} .label=${'Max Requests'} .description=${'Requests per source IP'}></dees-input-text>
199
+ <dees-input-text .key=${'rateLimitWindow'} .label=${'Window Seconds'}></dees-input-text>
172
200
  </dees-form>
173
201
  `,
174
202
  menuOptions: [
@@ -182,8 +210,10 @@ let OpsViewSourceProfiles = (() => {
182
210
  const data = await form.collectFormData();
183
211
  const ipAllowList = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
184
212
  const ipBlockList = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
185
- const parsed = data.maxConnections ? parseInt(String(data.maxConnections), 10) : NaN;
186
- const maxConnections = Number.isNaN(parsed) ? undefined : parsed;
213
+ const maxConnections = parseOptionalPositiveInteger(data.maxConnections);
214
+ const rateLimit = buildRateLimitFromFormData(data);
215
+ if (rateLimit === null)
216
+ return;
187
217
  await appstate.profilesTargetsStatePart.dispatchAction(appstate.createProfileAction, {
188
218
  name: String(data.name),
189
219
  description: data.description ? String(data.description) : undefined,
@@ -191,6 +221,7 @@ let OpsViewSourceProfiles = (() => {
191
221
  ...(ipAllowList.length > 0 ? { ipAllowList } : {}),
192
222
  ...(ipBlockList.length > 0 ? { ipBlockList } : {}),
193
223
  ...(maxConnections ? { maxConnections } : {}),
224
+ ...(rateLimit ? { rateLimit } : {}),
194
225
  },
195
226
  });
196
227
  modalArg.destroy();
@@ -210,6 +241,9 @@ let OpsViewSourceProfiles = (() => {
210
241
  <dees-input-list .key=${'ipAllowList'} .label=${'IP Allow List'} .placeholder=${'Add IP or CIDR...'} .value=${profile.security?.ipAllowList || []}></dees-input-list>
211
242
  <dees-input-list .key=${'ipBlockList'} .label=${'IP Block List'} .placeholder=${'Add IP or CIDR...'} .value=${profile.security?.ipBlockList || []}></dees-input-list>
212
243
  <dees-input-text .key=${'maxConnections'} .label=${'Max Connections'} .value=${String(profile.security?.maxConnections || '')}></dees-input-text>
244
+ <dees-input-checkbox .key=${'rateLimitEnabled'} .label=${'Enable Rate Limit'} .description=${'Per source IP. Exceeded requests receive 429.'} .value=${profile.security?.rateLimit?.enabled === true}></dees-input-checkbox>
245
+ <dees-input-text .key=${'rateLimitMaxRequests'} .label=${'Max Requests'} .description=${'Requests per source IP'} .value=${String(profile.security?.rateLimit?.maxRequests || '')}></dees-input-text>
246
+ <dees-input-text .key=${'rateLimitWindow'} .label=${'Window Seconds'} .value=${String(profile.security?.rateLimit?.window || '')}></dees-input-text>
213
247
  </dees-form>
214
248
  `,
215
249
  menuOptions: [
@@ -223,8 +257,10 @@ let OpsViewSourceProfiles = (() => {
223
257
  const data = await form.collectFormData();
224
258
  const ipAllowList = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
225
259
  const ipBlockList = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
226
- const parsed = data.maxConnections ? parseInt(String(data.maxConnections), 10) : NaN;
227
- const maxConnections = Number.isNaN(parsed) ? undefined : parsed;
260
+ const maxConnections = parseOptionalPositiveInteger(data.maxConnections);
261
+ const rateLimit = buildRateLimitFromFormData(data);
262
+ if (rateLimit === null)
263
+ return;
228
264
  await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateProfileAction, {
229
265
  id: profile.id,
230
266
  name: String(data.name),
@@ -233,6 +269,7 @@ let OpsViewSourceProfiles = (() => {
233
269
  ipAllowList,
234
270
  ipBlockList,
235
271
  ...(maxConnections ? { maxConnections } : {}),
272
+ ...(rateLimit ? { rateLimit } : {}),
236
273
  },
237
274
  });
238
275
  modalArg.destroy();
@@ -275,4 +312,4 @@ let OpsViewSourceProfiles = (() => {
275
312
  return OpsViewSourceProfiles = _classThis;
276
313
  })();
277
314
  export { OpsViewSourceProfiles };
278
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctc291cmNlcHJvZmlsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvbmV0d29yay9vcHMtdmlldy1zb3VyY2Vwcm9maWxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssVUFBVSxNQUFNLHNDQUFzQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQscUJBQXFCOzRCQURqQyxhQUFhLENBQUMseUJBQXlCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OztxQ0FBbkIsU0FBUSxXQUFXOzs7O3lDQUNuRCxLQUFLLEVBQUU7WUFDUiw0TEFBUyxhQUFhLDZCQUFiLGFBQWEscUdBQWlGO1lBRnpHLDZLQTZOQzs7OztRQTNOQyx1RkFBeUQsUUFBUSxDQUFDLHdCQUF3QixDQUFDLFFBQVEsRUFBRyxFQUFDO1FBQXZHLElBQVMsYUFBYSxtREFBaUY7UUFBdkcsSUFBUyxhQUFhLHlEQUFpRjtRQUV2RztZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyx3QkFBd0IsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRTtnQkFDNUUsSUFBSSxDQUFDLGFBQWEsR0FBRyxRQUFRLENBQUM7WUFDaEMsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxHQUFHLENB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
315
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctc291cmNlcHJvZmlsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvbmV0d29yay9vcHMtdmlldy1zb3VyY2Vwcm9maWxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssVUFBVSxNQUFNLHNDQUFzQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7QUFFOUQsU0FBUyw0QkFBNEIsQ0FBQyxLQUFjO0lBQ2xELE1BQU0sTUFBTSxHQUFHLE9BQU8sS0FBSyxLQUFLLFFBQVE7UUFDdEMsQ0FBQyxDQUFDLEtBQUs7UUFDUCxDQUFDLENBQUMsT0FBTyxLQUFLLEtBQUssUUFBUTtZQUN6QixDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsRUFBRSxFQUFFLENBQUM7WUFDNUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7SUFDakIsT0FBTyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQ3JFLENBQUM7QUFFRCxTQUFTLDBCQUEwQixDQUFDLElBQXlCO0lBQzNELElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQztRQUNwQyxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFD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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "13.41.1",
4
+ "version": "13.42.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -17,8 +17,8 @@
17
17
  "devDependencies": {
18
18
  "@git.zone/tsbuild": "^4.4.2",
19
19
  "@git.zone/tsbundle": "^2.10.4",
20
- "@git.zone/tsdocker": "^2.4.0",
21
20
  "@git.zone/tsdeno": "^1.5.0",
21
+ "@git.zone/tsdocker": "^2.4.0",
22
22
  "@git.zone/tsrun": "^2.0.4",
23
23
  "@git.zone/tstest": "^3.6.6",
24
24
  "@git.zone/tswatch": "^3.3.5",
@@ -50,7 +50,7 @@
50
50
  "@push.rocks/smartnetwork": "^4.7.2",
51
51
  "@push.rocks/smartpath": "^6.0.0",
52
52
  "@push.rocks/smartpromise": "^4.2.4",
53
- "@push.rocks/smartproxy": "^27.12.2",
53
+ "@push.rocks/smartproxy": "^27.12.4",
54
54
  "@push.rocks/smartradius": "^1.3.0",
55
55
  "@push.rocks/smartrequest": "^5.0.3",
56
56
  "@push.rocks/smartrx": "^3.0.10",
@@ -60,7 +60,7 @@
60
60
  "@push.rocks/taskbuffer": "^8.0.2",
61
61
  "@serve.zone/catalog": "^2.12.4",
62
62
  "@serve.zone/interfaces": "^5.8.0",
63
- "@serve.zone/remoteingress": "^4.22.3",
63
+ "@serve.zone/remoteingress": "^4.22.4",
64
64
  "@tsclass/tsclass": "^9.5.1",
65
65
  "@types/qrcode": "^1.5.6",
66
66
  "lru-cache": "^11.4.0",
package/readme.md CHANGED
@@ -146,6 +146,80 @@ dcrouter keeps generated and operator-created routes separate so automation can
146
146
 
147
147
  System routes are persisted with stable `systemKey` values. API-created routes are the editable route layer intended for operators and automation.
148
148
 
149
+ ## Route Source Policies
150
+
151
+ API-created route records pass `metadata.sourcePolicy` alongside the SmartProxy route config to express ordered source and path policy variants without duplicating whole routes by hand. A source policy contains ordered `bindings`, each pointing at a source profile id through `sourceProfileRef`. Dashboard presets resolve seeded profile names to ids before saving.
152
+
153
+ Runtime behavior:
154
+
155
+ - Source matching uses the referenced `SourceProfile.security.ipAllowList`.
156
+ - Bindings are evaluated in order and the first matching source profile wins.
157
+ - A matched binding that exceeds its configured rate or connection limit is terminal and returns `429`; dcrouter does not fall through to later bindings.
158
+ - Source-policy rate limits are always keyed by source IP; dcrouter ignores `path` and `header` keying on source-policy binding and path-policy overrides.
159
+ - A public fallback binding must be last and must use `*`, or both `0.0.0.0/0` and `::/0`, in `security.ipAllowList`.
160
+ - Create/update paths reject source policies with missing source profiles, source profiles without source matches, missing final all-source fallback, or any all-source binding that shadows later bindings; persisted invalid policies fail closed at compile time.
161
+ - Server-side caps bound policy expansion to 16 source bindings, 12 path policies per binding, 64 path patterns per path policy, 256 characters and 8 wildcards per custom path pattern, and 512 compiled SmartProxy route-port variants per stored route.
162
+
163
+ Path policies let a source binding override rate limits or connection limits for specific path classes. dcrouter currently ships Gitea-oriented classes: `git-smart-http`, `static`, `normal-html`, `expensive-html`, `raw`, and `archive`. Path-specific variants win over the same binding's fallback; if every path policy is path-specific, dcrouter adds a source-level fallback route for unmatched paths so normal browsing cannot fall through to a later source binding. The Gitea preset keeps `git-smart-http` high-limit and separate from HTML crawling paths so normal `git clone`, `git fetch`, `git push`, and Git LFS traffic are not subject to the lower HTML crawler limits.
164
+
165
+ ```typescript
166
+ const trustedProfileId = 'source-profile-id-trusted';
167
+ const publicProfileId = 'source-profile-id-public';
168
+
169
+ const createRoutePayload = {
170
+ route: {
171
+ name: 'public-gitea',
172
+ match: { domains: ['code.example.com'], ports: [443] },
173
+ action: {
174
+ type: 'forward',
175
+ targets: [{ host: '10.10.0.20', port: 3000 }],
176
+ tls: { mode: 'terminate', certificate: 'auto' },
177
+ },
178
+ },
179
+ metadata: {
180
+ sourcePolicy: {
181
+ bindings: [
182
+ {
183
+ sourceProfileRef: trustedProfileId,
184
+ maxConnections: 5000,
185
+ onExceeded: { type: '429' },
186
+ },
187
+ {
188
+ sourceProfileRef: publicProfileId,
189
+ onExceeded: { type: '429' },
190
+ pathPolicies: [
191
+ {
192
+ pathClass: 'git-smart-http',
193
+ rateLimit: { enabled: true, maxRequests: 1200, window: 60, keyBy: 'ip' },
194
+ },
195
+ {
196
+ pathClass: 'static',
197
+ rateLimit: { enabled: true, maxRequests: 600, window: 60, keyBy: 'ip' },
198
+ },
199
+ {
200
+ pathClass: 'raw',
201
+ rateLimit: { enabled: true, maxRequests: 120, window: 60, keyBy: 'ip' },
202
+ },
203
+ {
204
+ pathClass: 'archive',
205
+ rateLimit: { enabled: true, maxRequests: 30, window: 60, keyBy: 'ip' },
206
+ },
207
+ {
208
+ pathClass: 'expensive-html',
209
+ rateLimit: { enabled: true, maxRequests: 30, window: 60, keyBy: 'ip' },
210
+ },
211
+ {
212
+ pathClass: 'normal-html',
213
+ rateLimit: { enabled: true, maxRequests: 120, window: 60, keyBy: 'ip' },
214
+ },
215
+ ],
216
+ },
217
+ ],
218
+ },
219
+ },
220
+ };
221
+ ```
222
+
149
223
  ## Production-Flavored Example
150
224
 
151
225
  ```typescript
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.41.1',
6
+ version: '13.42.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -68,11 +68,38 @@ export class DbSeeder {
68
68
  }
69
69
 
70
70
  const DEFAULT_PROFILES: Array<NonNullable<ISeedData['profiles']>[number]> = [
71
+ {
72
+ name: 'TRUSTED NETWORKS',
73
+ description: 'Trusted office, VPN, localhost, and private-network sources with high connection allowance',
74
+ security: {
75
+ ipAllowList: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1', '::1'],
76
+ maxConnections: 5000,
77
+ },
78
+ },
79
+ {
80
+ name: 'AI CRAWLERS',
81
+ description: 'Add verified crawler CIDRs before assigning this profile in a source policy',
82
+ security: {
83
+ ipAllowList: [],
84
+ rateLimit: {
85
+ enabled: true,
86
+ maxRequests: 30,
87
+ window: 60,
88
+ keyBy: 'ip',
89
+ },
90
+ },
91
+ },
71
92
  {
72
93
  name: 'PUBLIC',
73
- description: 'Allow all traffic no IP restrictions',
94
+ description: 'Public fallback source profile with per-IP request limiting',
74
95
  security: {
75
96
  ipAllowList: ['*'],
97
+ rateLimit: {
98
+ enabled: true,
99
+ maxRequests: 120,
100
+ window: 60,
101
+ keyBy: 'ip',
102
+ },
76
103
  },
77
104
  },
78
105
  {
@@ -7,6 +7,7 @@ import type {
7
7
  IRouteMetadata,
8
8
  IRoute,
9
9
  IRouteSecurity,
10
+ IRouteSourcePolicy,
10
11
  } from '../../ts_interfaces/data/route-management.js';
11
12
 
12
13
  const MAX_INHERITANCE_DEPTH = 5;
@@ -107,7 +108,7 @@ export class ReferenceResolver {
107
108
 
108
109
  // If force-deleting with referencing routes, clear refs but keep resolved values
109
110
  if (affectedIds.length > 0) {
110
- await this.clearProfileRefsOnRoutes(affectedIds);
111
+ await this.clearProfileRefsOnRoutes(id, affectedIds, storedRoutes);
111
112
  logger.log('warn', `Force-deleted profile '${profile.name}'; cleared refs on ${affectedIds.length} route(s)`);
112
113
  } else {
113
114
  logger.log('info', `Deleted source profile '${profile.name}' (${id})`);
@@ -131,15 +132,22 @@ export class ReferenceResolver {
131
132
  return [...this.profiles.values()];
132
133
  }
133
134
 
135
+ public resolveSourceProfileSecurity(profileId: string): IRouteSecurity | null {
136
+ const resolvedSecurity = this.resolveSourceProfile(profileId);
137
+ return resolvedSecurity ? this.cloneSecurityFields(resolvedSecurity) : null;
138
+ }
139
+
134
140
  public getProfileUsage(storedRoutes: Map<string, IRoute>): Map<string, Array<{ id: string; routeName: string }>> {
135
141
  const usage = new Map<string, Array<{ id: string; routeName: string }>>();
136
142
  for (const profile of this.profiles.values()) {
137
143
  usage.set(profile.id, []);
138
144
  }
139
145
  for (const [routeId, stored] of storedRoutes) {
140
- const ref = stored.metadata?.sourceProfileRef;
141
- if (ref && usage.has(ref)) {
142
- usage.get(ref)!.push({ id: routeId, routeName: stored.route.name || routeId });
146
+ const refs = this.getSourceProfileRefsFromMetadata(stored.metadata);
147
+ for (const ref of refs) {
148
+ if (usage.has(ref)) {
149
+ usage.get(ref)!.push({ id: routeId, routeName: stored.route.name || routeId });
150
+ }
143
151
  }
144
152
  }
145
153
  return usage;
@@ -151,7 +159,7 @@ export class ReferenceResolver {
151
159
  ): Array<{ id: string; routeName: string }> {
152
160
  const routes: Array<{ id: string; routeName: string }> = [];
153
161
  for (const [routeId, stored] of storedRoutes) {
154
- if (stored.metadata?.sourceProfileRef === profileId) {
162
+ if (this.metadataUsesSourceProfile(stored.metadata, profileId)) {
155
163
  routes.push({ id: routeId, routeName: stored.route.name || routeId });
156
164
  }
157
165
  }
@@ -290,7 +298,15 @@ export class ReferenceResolver {
290
298
  ): { route: plugins.smartproxy.IRouteConfig; metadata: IRouteMetadata } {
291
299
  const resolvedMetadata: IRouteMetadata = { ...metadata };
292
300
 
293
- if (resolvedMetadata.sourceProfileRef) {
301
+ if (resolvedMetadata.sourcePolicy?.bindings.length) {
302
+ const resolvedSourcePolicy = this.resolveRouteSourcePolicy(resolvedMetadata.sourcePolicy);
303
+ if (resolvedSourcePolicy) {
304
+ resolvedMetadata.sourcePolicy = resolvedSourcePolicy;
305
+ resolvedMetadata.sourceProfileRef = undefined;
306
+ resolvedMetadata.sourceProfileName = undefined;
307
+ resolvedMetadata.lastResolvedAt = Date.now();
308
+ }
309
+ } else if (resolvedMetadata.sourceProfileRef) {
294
310
  const resolvedSecurity = this.resolveSourceProfile(resolvedMetadata.sourceProfileRef);
295
311
  if (resolvedSecurity) {
296
312
  const profile = this.profiles.get(resolvedMetadata.sourceProfileRef);
@@ -336,7 +352,7 @@ export class ReferenceResolver {
336
352
  public async findRoutesByProfileRef(profileId: string): Promise<string[]> {
337
353
  const docs = await RouteDoc.findAll();
338
354
  return docs
339
- .filter((doc) => doc.metadata?.sourceProfileRef === profileId)
355
+ .filter((doc) => this.metadataUsesSourceProfile(doc.metadata, profileId))
340
356
  .map((doc) => doc.id);
341
357
  }
342
358
 
@@ -350,7 +366,7 @@ export class ReferenceResolver {
350
366
  public findRoutesByProfileRefSync(profileId: string, storedRoutes: Map<string, IRoute>): string[] {
351
367
  const ids: string[] = [];
352
368
  for (const [routeId, stored] of storedRoutes) {
353
- if (stored.metadata?.sourceProfileRef === profileId) {
369
+ if (this.metadataUsesSourceProfile(stored.metadata, profileId)) {
354
370
  ids.push(routeId);
355
371
  }
356
372
  }
@@ -371,6 +387,41 @@ export class ReferenceResolver {
371
387
  // Private: source profile resolution with inheritance
372
388
  // =========================================================================
373
389
 
390
+ private resolveRouteSourcePolicy(sourcePolicy: IRouteSourcePolicy): IRouteSourcePolicy | undefined {
391
+ const bindings = sourcePolicy.bindings
392
+ .map((binding) => {
393
+ const profile = this.profiles.get(binding.sourceProfileRef);
394
+ if (!profile) {
395
+ logger.log('warn', `Source profile '${binding.sourceProfileRef}' not found during source policy resolution`);
396
+ return binding;
397
+ }
398
+ return {
399
+ ...binding,
400
+ sourceProfileName: profile.name,
401
+ };
402
+ })
403
+ .filter((binding) => binding.sourceProfileRef);
404
+
405
+ return bindings.length > 0 ? { bindings } : undefined;
406
+ }
407
+
408
+ private metadataUsesSourceProfile(metadata: IRouteMetadata | undefined, profileId: string): boolean {
409
+ return this.getSourceProfileRefsFromMetadata(metadata).includes(profileId);
410
+ }
411
+
412
+ private getSourceProfileRefsFromMetadata(metadata: IRouteMetadata | undefined): string[] {
413
+ const refs = new Set<string>();
414
+ if (metadata?.sourceProfileRef) {
415
+ refs.add(metadata.sourceProfileRef);
416
+ }
417
+ for (const binding of metadata?.sourcePolicy?.bindings || []) {
418
+ if (binding.sourceProfileRef) {
419
+ refs.add(binding.sourceProfileRef);
420
+ }
421
+ }
422
+ return [...refs];
423
+ }
424
+
374
425
  private resolveSourceProfile(
375
426
  profileId: string,
376
427
  visited: Set<string> = new Set(),
@@ -550,21 +601,50 @@ export class ReferenceResolver {
550
601
  // Private: ref cleanup on force-delete
551
602
  // =========================================================================
552
603
 
553
- private async clearProfileRefsOnRoutes(routeIds: string[]): Promise<void> {
604
+ private async clearProfileRefsOnRoutes(
605
+ profileId: string,
606
+ routeIds: string[],
607
+ storedRoutes?: Map<string, IRoute>,
608
+ ): Promise<void> {
554
609
  for (const routeId of routeIds) {
555
610
  const doc = await RouteDoc.findById(routeId);
556
611
  if (doc?.metadata) {
557
- doc.metadata = {
558
- ...doc.metadata,
559
- sourceProfileRef: undefined,
560
- sourceProfileName: undefined,
561
- };
612
+ doc.metadata = this.clearSourceProfileFromMetadata(doc.metadata, profileId);
562
613
  doc.updatedAt = Date.now();
563
614
  await doc.save();
564
615
  }
616
+
617
+ const storedRoute = storedRoutes?.get(routeId);
618
+ if (storedRoute?.metadata) {
619
+ storedRoute.metadata = this.clearSourceProfileFromMetadata(storedRoute.metadata, profileId);
620
+ storedRoute.updatedAt = Date.now();
621
+ }
565
622
  }
566
623
  }
567
624
 
625
+ private clearSourceProfileFromMetadata(metadata: IRouteMetadata, profileId: string): IRouteMetadata {
626
+ const sourcePolicy = metadata.sourcePolicy?.bindings?.length
627
+ ? {
628
+ bindings: metadata.sourcePolicy.bindings.filter(
629
+ (binding) => binding.sourceProfileRef !== profileId,
630
+ ),
631
+ }
632
+ : undefined;
633
+
634
+ const nextMetadata: IRouteMetadata = {
635
+ ...metadata,
636
+ sourceProfileRef: metadata.sourceProfileRef === profileId ? undefined : metadata.sourceProfileRef,
637
+ sourceProfileName: metadata.sourceProfileRef === profileId ? undefined : metadata.sourceProfileName,
638
+ sourcePolicy: sourcePolicy?.bindings.length ? sourcePolicy : undefined,
639
+ };
640
+
641
+ if (!nextMetadata.sourceProfileRef && !nextMetadata.sourcePolicy && !nextMetadata.networkTargetRef) {
642
+ nextMetadata.lastResolvedAt = undefined;
643
+ }
644
+
645
+ return nextMetadata;
646
+ }
647
+
568
648
  private async clearTargetRefsOnRoutes(routeIds: string[]): Promise<void> {
569
649
  for (const routeId of routeIds) {
570
650
  const doc = await RouteDoc.findById(routeId);