@cat-factory/integrations 0.166.22 → 0.168.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 (70) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/modules/environments/EnvironmentProvisioningService.d.ts +36 -2
  6. package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -1
  7. package/dist/modules/environments/EnvironmentProvisioningService.js +56 -1
  8. package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -1
  9. package/dist/modules/environments/environmentDiagnostics.d.ts +51 -0
  10. package/dist/modules/environments/environmentDiagnostics.d.ts.map +1 -0
  11. package/dist/modules/environments/environmentDiagnostics.js +352 -0
  12. package/dist/modules/environments/environmentDiagnostics.js.map +1 -0
  13. package/dist/modules/environments/environmentRemediation.d.ts +53 -0
  14. package/dist/modules/environments/environmentRemediation.d.ts.map +1 -0
  15. package/dist/modules/environments/environmentRemediation.js +72 -0
  16. package/dist/modules/environments/environmentRemediation.js.map +1 -0
  17. package/dist/modules/environments/environments.logic.d.ts +7 -0
  18. package/dist/modules/environments/environments.logic.d.ts.map +1 -1
  19. package/dist/modules/environments/environments.logic.js +28 -0
  20. package/dist/modules/environments/environments.logic.js.map +1 -1
  21. package/dist/modules/kubernetes/KubernetesEnvironmentProvider.d.ts +25 -2
  22. package/dist/modules/kubernetes/KubernetesEnvironmentProvider.d.ts.map +1 -1
  23. package/dist/modules/kubernetes/KubernetesEnvironmentProvider.js +106 -21
  24. package/dist/modules/kubernetes/KubernetesEnvironmentProvider.js.map +1 -1
  25. package/dist/modules/kubernetes/kubernetes-diagnostics.d.ts +31 -0
  26. package/dist/modules/kubernetes/kubernetes-diagnostics.d.ts.map +1 -0
  27. package/dist/modules/kubernetes/kubernetes-diagnostics.js +350 -0
  28. package/dist/modules/kubernetes/kubernetes-diagnostics.js.map +1 -0
  29. package/dist/modules/kubernetes/kubernetes-environment.logic.d.ts +8 -1
  30. package/dist/modules/kubernetes/kubernetes-environment.logic.d.ts.map +1 -1
  31. package/dist/modules/kubernetes/kubernetes-environment.logic.js +18 -4
  32. package/dist/modules/kubernetes/kubernetes-environment.logic.js.map +1 -1
  33. package/dist/modules/kubernetes/kubernetes.logic.d.ts +42 -2
  34. package/dist/modules/kubernetes/kubernetes.logic.d.ts.map +1 -1
  35. package/dist/modules/kubernetes/kubernetes.logic.js +106 -2
  36. package/dist/modules/kubernetes/kubernetes.logic.js.map +1 -1
  37. package/dist/modules/providers/OpenRouterCatalogService.d.ts.map +1 -1
  38. package/dist/modules/providers/OpenRouterCatalogService.js +2 -30
  39. package/dist/modules/providers/OpenRouterCatalogService.js.map +1 -1
  40. package/dist/modules/providers/openRouterModels.d.ts +12 -0
  41. package/dist/modules/providers/openRouterModels.d.ts.map +1 -0
  42. package/dist/modules/providers/openRouterModels.js +107 -0
  43. package/dist/modules/providers/openRouterModels.js.map +1 -0
  44. package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts +75 -0
  45. package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts.map +1 -0
  46. package/dist/modules/serviceCatalog/BackstageCatalogClient.js +410 -0
  47. package/dist/modules/serviceCatalog/BackstageCatalogClient.js.map +1 -0
  48. package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts +85 -0
  49. package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts.map +1 -0
  50. package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js +177 -0
  51. package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js.map +1 -0
  52. package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts +173 -0
  53. package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts.map +1 -0
  54. package/dist/modules/serviceCatalog/backstage-catalog.logic.js +362 -0
  55. package/dist/modules/serviceCatalog/backstage-catalog.logic.js.map +1 -0
  56. package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts +42 -0
  57. package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts.map +1 -0
  58. package/dist/modules/serviceCatalog/serviceCatalogAuth.js +201 -0
  59. package/dist/modules/serviceCatalog/serviceCatalogAuth.js.map +1 -0
  60. package/dist/modules/shared/base64.d.ts +19 -0
  61. package/dist/modules/shared/base64.d.ts.map +1 -0
  62. package/dist/modules/shared/base64.js +75 -0
  63. package/dist/modules/shared/base64.js.map +1 -0
  64. package/dist/modules/tracker/TicketTrackerService.js +1 -1
  65. package/dist/modules/tracker/TicketTrackerService.js.map +1 -1
  66. package/package.json +4 -4
  67. package/dist/modules/tracker/base64.d.ts +0 -2
  68. package/dist/modules/tracker/base64.d.ts.map +0 -1
  69. package/dist/modules/tracker/base64.js +0 -18
  70. package/dist/modules/tracker/base64.js.map +0 -1
@@ -0,0 +1,362 @@
1
+ import { RESERVED_CAPABILITY_TAGS, reservedCapabilityNearMiss } from '@cat-factory/contracts';
2
+ // ---------------------------------------------------------------------------
3
+ // Backstage-specific PURE logic: turning a Software Catalog entity into the platform's neutral
4
+ // `ServiceCatalogEntry`, and back the other way for the query it takes to fetch one.
5
+ //
6
+ // Everything that knows what Backstage calls a service, an owner or an API definition lives in
7
+ // this file and its client sibling. The importer that consumes the result
8
+ // (`ServiceCatalogSyncService` in `@cat-factory/agents`) sees only the neutral vocabulary, which
9
+ // is what makes a second portal product a second adapter rather than a branch inside the import.
10
+ //
11
+ // No I/O, so every mapping rule below is unit-testable without a live instance.
12
+ // ---------------------------------------------------------------------------
13
+ /** The namespace Backstage uses when an entity declares none. */
14
+ const DEFAULT_NAMESPACE = 'default';
15
+ /** How long a composed description may run: the catalog's own limit for the field. */
16
+ export const MAX_DESCRIPTION_CHARS = 20_000;
17
+ /** How long a summary line may run: the catalog's own limit for the field. */
18
+ const MAX_SUMMARY_CHARS = 400;
19
+ /** How many capability tags one imported service may carry. */
20
+ const MAX_CAPABILITIES = 20;
21
+ /**
22
+ * How large one API definition may be and still be stored.
23
+ *
24
+ * The same ceiling a direct upload is held to, deliberately: a document past it is not served to
25
+ * an agent in any useful form (the contract render caps far lower and says what it dropped), and
26
+ * letting the import store what an upload would be refused would make the two supply routes
27
+ * disagree about what a contract is.
28
+ */
29
+ export const MAX_DEFINITION_CHARS = 1_000_000;
30
+ /**
31
+ * Parse one of the several shapes Backstage accepts for a reference, with `defaultKind` filling
32
+ * the one a compact reference omits.
33
+ *
34
+ * Compact references are the norm rather than an edge case: `spec.owner: payments` and
35
+ * `providesApis: [orders-api]` are what a hand-written `catalog-info.yaml` contains, and the
36
+ * kind is implied by the FIELD they appear in. Returns null when there is no name at all, so a
37
+ * malformed reference is reported rather than resolved to something plausible.
38
+ */
39
+ export function parseEntityRef(raw, defaultKind) {
40
+ const trimmed = raw.trim();
41
+ if (!trimmed)
42
+ return null;
43
+ const colon = trimmed.indexOf(':');
44
+ const kind = colon === -1 ? defaultKind : trimmed.slice(0, colon);
45
+ const rest = colon === -1 ? trimmed : trimmed.slice(colon + 1);
46
+ const slash = rest.indexOf('/');
47
+ const namespace = slash === -1 ? DEFAULT_NAMESPACE : rest.slice(0, slash);
48
+ const name = slash === -1 ? rest : rest.slice(slash + 1);
49
+ if (!kind.trim() || !name.trim())
50
+ return null;
51
+ return {
52
+ kind: kind.trim().toLowerCase(),
53
+ namespace: (namespace.trim() || DEFAULT_NAMESPACE).toLowerCase(),
54
+ name: name.trim(),
55
+ };
56
+ }
57
+ /** Render a reference in the canonical `kind:namespace/name` form the API addresses entities by. */
58
+ export function formatEntityRef(ref) {
59
+ return `${ref.kind}:${ref.namespace}/${ref.name}`;
60
+ }
61
+ /**
62
+ * The canonical reference for an entity as it came back, or null when it carries no name.
63
+ *
64
+ * Kind AND namespace are lower-cased, exactly as {@link parseEntityRef} does to the references it
65
+ * reads out of `providesApis` and `spec.owner`. Both halves matter: an entity declaring
66
+ * `namespace: Payments` would otherwise be recorded as `component:Payments/orders` while every
67
+ * reference the platform builds for the same entity resolves to `payments/...`, so the stored
68
+ * provenance would address nothing and the sync's `sourcePath` comparison would be against a value
69
+ * the canonical form can never produce.
70
+ */
71
+ export function entityRef(entity) {
72
+ const name = entity.metadata?.name?.trim();
73
+ if (!name)
74
+ return null;
75
+ return formatEntityRef({
76
+ kind: (entity.kind ?? 'component').toLowerCase(),
77
+ namespace: entity.metadata?.namespace?.trim().toLowerCase() || DEFAULT_NAMESPACE,
78
+ name,
79
+ });
80
+ }
81
+ /**
82
+ * The foundational-service id an entity is imported under: its name as a lower-kebab slug,
83
+ * prefixed with its namespace when that is not the default one.
84
+ *
85
+ * The namespace prefix is what keeps the id unique. Backstage namespaces exist precisely so two
86
+ * teams can both own an `api` component, and slugging on the name alone would collapse them onto
87
+ * one catalog entry, silently, and in favour of whichever the portal paged first. The `default`
88
+ * namespace is left off because it is the one every hand-written entity lands in, and prefixing
89
+ * it would put `default-` in front of an organisation's entire estate.
90
+ *
91
+ * Null when nothing slug-shaped survives (a name of only punctuation), which the caller reports
92
+ * as a skipped entity rather than importing under a generated id nobody can look up.
93
+ */
94
+ export function serviceIdForEntity(entity) {
95
+ return namespacedSlug(entity);
96
+ }
97
+ /**
98
+ * The slug an entity is stored under: its name, prefixed with its namespace when that is not the
99
+ * default one. Shared by {@link serviceIdForEntity} and {@link toServiceCatalogApi}.
100
+ *
101
+ * ONE rule for both, because the two ids collide for the same reason and the fix is the same:
102
+ * `payments/orders` and `billing/orders` are two different API entities whose names are equal, and
103
+ * deriving a contract id from the name alone silently keeps whichever the portal answered first.
104
+ * Component ids were prefixed from the start; interfaces were not, and there is no reading of the
105
+ * catalog under which one of these needs the namespace and the other does not.
106
+ */
107
+ function namespacedSlug(entity) {
108
+ const name = slugify(entity.metadata?.name ?? '');
109
+ if (!name)
110
+ return null;
111
+ const namespace = entity.metadata?.namespace?.trim().toLowerCase();
112
+ const prefix = namespace && namespace !== DEFAULT_NAMESPACE ? `${slugify(namespace)}-` : '';
113
+ return slugify(`${prefix}${name}`) || null;
114
+ }
115
+ /**
116
+ * A lower-kebab slug, or the empty string when the input holds nothing slug-shaped.
117
+ *
118
+ * Capped at the 64 characters the catalog's own id rule allows, and the cap is a plain prefix by
119
+ * design: a truncated slug still has to be STABLE across imports, and any cleverer strategy (a
120
+ * hash suffix, dropping interior words) would produce a different id the day a name grows.
121
+ */
122
+ export function slugify(value) {
123
+ return value
124
+ .toLowerCase()
125
+ .replace(/[^a-z0-9]+/g, '-')
126
+ .replace(/^-+|-+$/g, '')
127
+ .slice(0, 64)
128
+ .replace(/-+$/g, '');
129
+ }
130
+ /**
131
+ * The format an API entity's declared `spec.type` maps to, or null when the platform serves none
132
+ * for it.
133
+ *
134
+ * Null rather than a fallback, and that is the whole point of the function. Backstage's API type
135
+ * is an open vocabulary (`trpc`, `sql`, `soap` and anything an organisation invents all appear),
136
+ * and storing one of those AS OpenAPI would produce a contract that fails the OpenAPI parse and
137
+ * lists zero operations, which reads to an Architect as a fully-specified service that offers no
138
+ * endpoints. The importer reports a null as a skipped interface instead, which says the opposite
139
+ * and is true.
140
+ */
141
+ export function apiContractFormatForType(type) {
142
+ switch (type?.trim().toLowerCase()) {
143
+ case 'openapi':
144
+ return 'openapi';
145
+ case 'asyncapi':
146
+ return 'asyncapi';
147
+ case 'graphql':
148
+ return 'graphql';
149
+ case 'grpc':
150
+ return 'grpc';
151
+ default:
152
+ return null;
153
+ }
154
+ }
155
+ /** The annotation carrying a component's TechDocs location, when it publishes docs. */
156
+ const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';
157
+ /** The annotation carrying a component's source location (its repository). */
158
+ const SOURCE_LOCATION_ANNOTATION = 'backstage.io/source-location';
159
+ /**
160
+ * Map one catalog entity onto the platform's neutral entry, with `apis` supplied separately
161
+ * (they are separate entities, fetched in one batch rather than one request per service).
162
+ *
163
+ * Returns null when the entity yields no usable identity, which the caller counts as skipped.
164
+ */
165
+ export function toServiceCatalogEntry(entity, apis) {
166
+ const id = serviceIdForEntity(entity);
167
+ const ref = entityRef(entity);
168
+ if (!id || !ref)
169
+ return null;
170
+ const metadata = entity.metadata ?? {};
171
+ return {
172
+ id,
173
+ name: (metadata.title?.trim() || metadata.name?.trim() || id).slice(0, 200),
174
+ summary: entitySummary(entity),
175
+ description: composeDescription(entity),
176
+ capabilities: entityCapabilities(entity),
177
+ ref,
178
+ apis,
179
+ };
180
+ }
181
+ /**
182
+ * The one-line relevance signal.
183
+ *
184
+ * Falls back to the facts the entity DOES carry rather than to a placeholder, because a catalog
185
+ * whose every summary reads "no description" is a catalog a reader stops reading. A component's
186
+ * type and owner are the two things a portal always has, and together they are a real answer to
187
+ * "is this the service I am looking for".
188
+ */
189
+ export function entitySummary(entity) {
190
+ const described = firstLine(entity.metadata?.description ?? '');
191
+ if (described)
192
+ return described.slice(0, MAX_SUMMARY_CHARS);
193
+ const type = entity.spec?.type?.trim() || (entity.kind ?? 'component').toLowerCase();
194
+ const owner = ownerLabel(entity.spec?.owner);
195
+ const summary = owner ? `${type} owned by ${owner}` : `${type} with no recorded owner`;
196
+ return summary.slice(0, MAX_SUMMARY_CHARS);
197
+ }
198
+ /**
199
+ * The composed description: the portal's structured facts as labelled lines, then the entity's
200
+ * own prose.
201
+ *
202
+ * The ORDER is the load-bearing part. Ownership is what a triage reader needs first and it is
203
+ * also what a cap must never drop, so the labelled facts come before the free text and the
204
+ * truncation falls on the prose. A description that lost its owner line to a long README excerpt
205
+ * would be the one field this composition exists to guarantee.
206
+ */
207
+ export function composeDescription(entity) {
208
+ const spec = entity.spec ?? {};
209
+ const metadata = entity.metadata ?? {};
210
+ const facts = [];
211
+ const owner = ownerLabel(spec.owner);
212
+ facts.push(owner ? `Owner: ${owner}` : 'Owner: not recorded in the catalog');
213
+ if (spec.system?.trim())
214
+ facts.push(`System: ${spec.system.trim()}`);
215
+ if (spec.domain?.trim())
216
+ facts.push(`Domain: ${spec.domain.trim()}`);
217
+ if (spec.lifecycle?.trim())
218
+ facts.push(`Lifecycle: ${spec.lifecycle.trim()}`);
219
+ if (spec.type?.trim())
220
+ facts.push(`Type: ${spec.type.trim()}`);
221
+ const source = metadata.annotations?.[SOURCE_LOCATION_ANNOTATION]?.trim();
222
+ if (source)
223
+ facts.push(`Source: ${source}`);
224
+ const docs = metadata.annotations?.[TECHDOCS_ANNOTATION]?.trim();
225
+ if (docs)
226
+ facts.push(`Docs: ${docs}`);
227
+ const links = (metadata.links ?? [])
228
+ .map((link) => describeLink(link))
229
+ .filter((line) => line !== null);
230
+ if (links.length > 0)
231
+ facts.push(`Links: ${links.join(', ')}`);
232
+ const prose = (metadata.description ?? '').trim();
233
+ const composed = prose ? `${facts.join('\n')}\n\n${prose}` : facts.join('\n');
234
+ return composed.slice(0, MAX_DESCRIPTION_CHARS);
235
+ }
236
+ function describeLink(link) {
237
+ const url = link.url?.trim();
238
+ if (!url)
239
+ return null;
240
+ const title = link.title?.trim();
241
+ return title ? `${title} (${url})` : url;
242
+ }
243
+ /**
244
+ * A readable owner: the reference's NAME, with the full reference beside it.
245
+ *
246
+ * Both halves, because they answer different questions. `payments` is what a human recognises
247
+ * and what an agent can put in a report; `group:default/payments` is what someone can look up in
248
+ * the portal. Rendering only the reference makes the estate read like machine output, and
249
+ * rendering only the name loses which namespace (and which kind of owner) it belongs to.
250
+ */
251
+ export function ownerLabel(owner) {
252
+ if (!owner?.trim())
253
+ return null;
254
+ const ref = parseEntityRef(owner, 'group');
255
+ if (!ref)
256
+ return null;
257
+ return `${ref.name} (${formatEntityRef(ref)})`;
258
+ }
259
+ /**
260
+ * The capability tags an imported service carries: the entity's own tags plus its declared type.
261
+ *
262
+ * A RESERVED platform tag is dropped rather than imported, and that is a rule rather than a cap.
263
+ * `asset-storage` and `generation-context` are spellings the platform assigns meaning to (the
264
+ * first makes a service selectable as a binary-output step's storage target), so accepting one
265
+ * from an external portal would let whoever edits a `catalog-info.yaml` enrol a component into a
266
+ * platform capability that nobody at this deployment chose. A NEAR-MISS of a reserved tag goes
267
+ * the same way, since importing it would put a tag in the catalog that reads as the reserved one
268
+ * and behaves as nothing.
269
+ */
270
+ export function entityCapabilities(entity) {
271
+ const raw = [...(entity.metadata?.tags ?? []), entity.spec?.type ?? ''];
272
+ const out = [];
273
+ for (const value of raw) {
274
+ const tag = slugify(value);
275
+ if (!tag)
276
+ continue;
277
+ if (RESERVED_CAPABILITY_TAGS.includes(tag) || reservedCapabilityNearMiss(tag))
278
+ continue;
279
+ if (out.includes(tag))
280
+ continue;
281
+ out.push(tag);
282
+ if (out.length >= MAX_CAPABILITIES)
283
+ break;
284
+ }
285
+ return out;
286
+ }
287
+ /** The API entity references one component declares, canonicalised and de-duplicated. */
288
+ export function providedApiRefs(entity) {
289
+ const out = [];
290
+ for (const raw of entity.spec?.providesApis ?? []) {
291
+ const ref = parseEntityRef(raw, 'api');
292
+ if (!ref)
293
+ continue;
294
+ const formatted = formatEntityRef(ref);
295
+ if (!out.includes(formatted))
296
+ out.push(formatted);
297
+ }
298
+ return out;
299
+ }
300
+ /**
301
+ * Map one API entity onto a neutral interface, or null when it carries nothing storable.
302
+ *
303
+ * The three null cases are deliberately not distinguished HERE (an unusable type, an empty
304
+ * definition and an oversized one all become one skipped interface), because the caller reports
305
+ * a count and every one of them has the same remedy at the portal: give the API entity a
306
+ * definition this platform can serve. What must not happen is any of them being stored anyway.
307
+ */
308
+ export function toServiceCatalogApi(entity) {
309
+ const ref = entityRef(entity);
310
+ const id = namespacedSlug(entity);
311
+ if (!ref || !id)
312
+ return null;
313
+ const definition = entity.spec?.definition;
314
+ if (typeof definition !== 'string')
315
+ return null;
316
+ const trimmed = definition.trim();
317
+ if (!trimmed || trimmed.length > MAX_DEFINITION_CHARS)
318
+ return null;
319
+ const format = apiContractFormatForType(entity.spec?.type);
320
+ if (!format)
321
+ return null;
322
+ return {
323
+ id,
324
+ title: (entity.metadata?.title?.trim() || entity.metadata?.name?.trim() || id).slice(0, 200),
325
+ format,
326
+ definition,
327
+ ref,
328
+ revision: entity.metadata?.etag?.trim() || null,
329
+ };
330
+ }
331
+ function firstLine(value) {
332
+ return value.trim().split(/\r?\n/)[0]?.trim() ?? '';
333
+ }
334
+ /**
335
+ * The `fields` projection the LIST request asks for.
336
+ *
337
+ * Naming the fields is what keeps a large estate's listing cheap: without it Backstage returns
338
+ * every entity in full, relations included, and an API entity's `spec.definition` can be
339
+ * hundreds of kilobytes. `spec.definition` is deliberately absent here: the definitions are
340
+ * read in the second, batched request, for only the APIs the imported components actually
341
+ * declare.
342
+ */
343
+ export const CATALOG_LIST_FIELDS = [
344
+ 'kind',
345
+ 'metadata.name',
346
+ 'metadata.namespace',
347
+ 'metadata.title',
348
+ 'metadata.description',
349
+ 'metadata.tags',
350
+ 'metadata.annotations',
351
+ 'metadata.links',
352
+ 'metadata.etag',
353
+ 'spec.type',
354
+ 'spec.lifecycle',
355
+ 'spec.owner',
356
+ 'spec.system',
357
+ 'spec.domain',
358
+ 'spec.providesApis',
359
+ ];
360
+ /** The `fields` projection the batched API-entity request asks for: the list plus definitions. */
361
+ export const API_FETCH_FIELDS = [...CATALOG_LIST_FIELDS, 'spec.definition'];
362
+ //# sourceMappingURL=backstage-catalog.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backstage-catalog.logic.js","sourceRoot":"","sources":["../../../src/modules/serviceCatalog/backstage-catalog.logic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { ServiceCatalogAuth, ServiceCatalogAuthMode } from '@cat-factory/contracts';
2
+ /** Read a sealed credential bag back into the discriminated auth it was written from. */
3
+ export declare function parseServiceCatalogAuth(plaintext: string, expected: ServiceCatalogAuthMode): ServiceCatalogAuth;
4
+ /**
5
+ * The headers one catalog request carries.
6
+ *
7
+ * `bearer` is the token an OAuth2 exchange produced, resolved once by the caller. It is a
8
+ * parameter rather than something fetched here so this stays pure and so one import pass cannot
9
+ * fan out into one token request per page.
10
+ */
11
+ export declare function serviceCatalogAuthHeaders(auth: ServiceCatalogAuth, bearer: string | null): Record<string, string>;
12
+ /** Whether this mode's bearer value has to be resolved before any request is built. */
13
+ export declare function requiresResolvedBearer(mode: ServiceCatalogAuthMode): boolean;
14
+ /**
15
+ * Mint the short-lived HS256 token Backstage's LEGACY external-access mode accepts: a JWT whose
16
+ * subject is the vendor's fixed `backstage-server`, signed with the HMAC key the configured
17
+ * shared secret decodes to.
18
+ *
19
+ * The secret is base64-DECODED into key bytes rather than used as UTF-8, because that is how the
20
+ * vendor derives its own key and how its documentation has operators generate the value
21
+ * (`openssl rand -base64 32`). Signing with the raw characters of a base64 secret produces a
22
+ * different key, so the choice is not a preference: it decides whether the token verifies at all.
23
+ * A secret that is not base64 is REFUSED here rather than falling back to its raw bytes: the two
24
+ * readings of one string are indistinguishable, so a fallback would sign half the deployments in
25
+ * the world with the wrong key and surface as a 401 blaming the credential.
26
+ */
27
+ export declare function mintLegacyBackstageToken(sharedSecret: string, nowSeconds: number): Promise<string>;
28
+ /** The form body an OAuth2 client-credentials exchange posts. */
29
+ export declare function clientCredentialsBody(auth: {
30
+ clientId: string;
31
+ clientSecret: string;
32
+ scope?: string;
33
+ audience?: string;
34
+ }): string;
35
+ /**
36
+ * The access token an OAuth2 token response carries, or null when the body is not one.
37
+ *
38
+ * Null rather than a throw so the caller can attach WHICH connection failed and what the provider
39
+ * answered; a bare parse error here would name neither.
40
+ */
41
+ export declare function readAccessToken(body: unknown): string | null;
42
+ //# sourceMappingURL=serviceCatalogAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceCatalogAuth.d.ts","sourceRoot":"","sources":["../../../src/modules/serviceCatalog/serviceCatalogAuth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AA0BxF,yFAAyF;AACzF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,sBAAsB,GAC/B,kBAAkB,CA0BpB;AAgED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxB;AAYD,uFAAuF;AACvF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAE5E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,MAAM,CAST;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,201 @@
1
+ import { UnavailableError, ValidationError } from '@cat-factory/kernel';
2
+ import { base64ToBytes, bytesToBase64Url, toBase64, toBase64Url } from '../shared/base64.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Turning a stored service-catalog credential into the headers one request carries.
5
+ //
6
+ // Five of the six modes are a pure function of the bag and are computed per request. The sixth
7
+ // (`oauth2-client-credentials`) needs a round trip to an identity provider, so the caller
8
+ // resolves it ONCE per import pass and passes the resulting bearer value down; there is
9
+ // deliberately no cache here, because the only two places a token could be held are a module
10
+ // global (banned) and a per-isolate map that workerd may discard between invocations, and an
11
+ // import pass is exactly the scope over which one token is both sufficient and safe.
12
+ // ---------------------------------------------------------------------------
13
+ /** How long a minted legacy service token is valid for. Short: it is minted per pass. */
14
+ const LEGACY_TOKEN_TTL_SECONDS = 600;
15
+ /**
16
+ * The `sub` Backstage's legacy service-to-service tokens carry.
17
+ *
18
+ * Fixed by the vendor rather than chosen here: its backend admits a legacy token only when the
19
+ * subject is this exact string, so it is a wire constant and not a configurable identity.
20
+ */
21
+ const LEGACY_TOKEN_SUBJECT = 'backstage-server';
22
+ /** Read a sealed credential bag back into the discriminated auth it was written from. */
23
+ export function parseServiceCatalogAuth(plaintext, expected) {
24
+ if (expected === 'none')
25
+ return { mode: 'none' };
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(plaintext);
29
+ }
30
+ catch {
31
+ // silent-catch-ok: the parse error names only JSON syntax, and what the caller has to be
32
+ // told is which connection's bag will not open. The throw below says that.
33
+ parsed = null;
34
+ }
35
+ const bag = parsed && typeof parsed === 'object' ? parsed : null;
36
+ const auth = bag ? readAuth(bag, expected) : null;
37
+ if (!auth) {
38
+ // A row whose stored mode and stored bag disagree is corrupt, not a misconfiguration, so the
39
+ // remedy is re-entering the credential rather than anything the operator can adjust. Named
40
+ // as `unavailable` for that reason: a `ValidationError` here would blame the request.
41
+ // The SHARED reason, not one of this integration's own: an unopenable credential bag is the
42
+ // same fact here as for a document source or a tracker, with the same two remedies, and the
43
+ // SPA already carries copy naming both. `details.source` is what tells them apart.
44
+ throw new UnavailableError(`The stored service-catalog credential does not hold a '${expected}' credential. Re-enter the connection.`, 'connection_credentials_unreadable', { source: 'service_catalog' });
45
+ }
46
+ return auth;
47
+ }
48
+ /**
49
+ * Narrow a parsed bag against ONE declared mode.
50
+ *
51
+ * Keyed off the column's mode rather than the bag's own, so a bag that claims a different mode
52
+ * than the row does is refused instead of quietly switching schemes: the column is what every
53
+ * management read and the request builder branch on, and a disagreement between the two is the
54
+ * one state where trusting the ciphertext over the plaintext is wrong.
55
+ */
56
+ function readAuth(bag, mode) {
57
+ switch (mode) {
58
+ case 'none':
59
+ return { mode: 'none' };
60
+ case 'static-token': {
61
+ const token = text(bag.token);
62
+ return token ? { mode, token } : null;
63
+ }
64
+ case 'legacy-shared-secret': {
65
+ const sharedSecret = text(bag.sharedSecret);
66
+ return sharedSecret ? { mode, sharedSecret } : null;
67
+ }
68
+ case 'oauth2-client-credentials': {
69
+ const tokenUrl = text(bag.tokenUrl);
70
+ const clientId = text(bag.clientId);
71
+ const clientSecret = text(bag.clientSecret);
72
+ if (!tokenUrl || !clientId || !clientSecret)
73
+ return null;
74
+ const scope = text(bag.scope);
75
+ const audience = text(bag.audience);
76
+ return {
77
+ mode,
78
+ tokenUrl,
79
+ clientId,
80
+ clientSecret,
81
+ ...(scope ? { scope } : {}),
82
+ ...(audience ? { audience } : {}),
83
+ };
84
+ }
85
+ case 'basic': {
86
+ const username = text(bag.username);
87
+ const password = text(bag.password);
88
+ return username && password ? { mode, username, password } : null;
89
+ }
90
+ case 'headers': {
91
+ const raw = Array.isArray(bag.headers) ? bag.headers : [];
92
+ const headers = [];
93
+ for (const entry of raw) {
94
+ if (!entry || typeof entry !== 'object')
95
+ continue;
96
+ const name = text(entry.name);
97
+ const value = text(entry.value);
98
+ if (name && value)
99
+ headers.push({ name, value });
100
+ }
101
+ return headers.length > 0 ? { mode, headers } : null;
102
+ }
103
+ }
104
+ }
105
+ function text(value) {
106
+ return typeof value === 'string' && value.trim() ? value : null;
107
+ }
108
+ /**
109
+ * The headers one catalog request carries.
110
+ *
111
+ * `bearer` is the token an OAuth2 exchange produced, resolved once by the caller. It is a
112
+ * parameter rather than something fetched here so this stays pure and so one import pass cannot
113
+ * fan out into one token request per page.
114
+ */
115
+ export function serviceCatalogAuthHeaders(auth, bearer) {
116
+ switch (auth.mode) {
117
+ case 'none':
118
+ return {};
119
+ case 'static-token':
120
+ return { authorization: `Bearer ${auth.token}` };
121
+ case 'legacy-shared-secret':
122
+ // Minted asynchronously, so the caller supplies it through the same channel the OAuth2
123
+ // token uses. Reaching this with no bearer means the mint was skipped, which would send an
124
+ // UNAUTHENTICATED request and read back as a rejected credential.
125
+ return bearerHeaders(bearer, 'legacy service token');
126
+ case 'oauth2-client-credentials':
127
+ return bearerHeaders(bearer, 'OAuth2 access token');
128
+ case 'basic':
129
+ return { authorization: `Basic ${toBase64(`${auth.username}:${auth.password}`)}` };
130
+ case 'headers':
131
+ return Object.fromEntries(auth.headers.map((header) => [header.name, header.value]));
132
+ }
133
+ }
134
+ function bearerHeaders(bearer, what) {
135
+ if (!bearer) {
136
+ throw new UnavailableError(`The service-catalog ${what} was not resolved before the request was built.`, 'service_catalog_token_unresolved');
137
+ }
138
+ return { authorization: `Bearer ${bearer}` };
139
+ }
140
+ /** Whether this mode's bearer value has to be resolved before any request is built. */
141
+ export function requiresResolvedBearer(mode) {
142
+ return mode === 'legacy-shared-secret' || mode === 'oauth2-client-credentials';
143
+ }
144
+ /**
145
+ * Mint the short-lived HS256 token Backstage's LEGACY external-access mode accepts: a JWT whose
146
+ * subject is the vendor's fixed `backstage-server`, signed with the HMAC key the configured
147
+ * shared secret decodes to.
148
+ *
149
+ * The secret is base64-DECODED into key bytes rather than used as UTF-8, because that is how the
150
+ * vendor derives its own key and how its documentation has operators generate the value
151
+ * (`openssl rand -base64 32`). Signing with the raw characters of a base64 secret produces a
152
+ * different key, so the choice is not a preference: it decides whether the token verifies at all.
153
+ * A secret that is not base64 is REFUSED here rather than falling back to its raw bytes: the two
154
+ * readings of one string are indistinguishable, so a fallback would sign half the deployments in
155
+ * the world with the wrong key and surface as a 401 blaming the credential.
156
+ */
157
+ export async function mintLegacyBackstageToken(sharedSecret, nowSeconds) {
158
+ const keyBytes = base64ToBytes(sharedSecret);
159
+ if (!keyBytes || keyBytes.length === 0) {
160
+ throw new ValidationError('The Backstage shared secret must be base64 (the value `backend.auth.keys[].secret` holds). ' +
161
+ 'If your instance authenticates some other way, use a static token or explicit headers instead.', { reason: 'service_catalog_shared_secret_not_base64' });
162
+ }
163
+ const header = toBase64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
164
+ const payload = toBase64Url(JSON.stringify({
165
+ sub: LEGACY_TOKEN_SUBJECT,
166
+ iat: nowSeconds,
167
+ exp: nowSeconds + LEGACY_TOKEN_TTL_SECONDS,
168
+ }));
169
+ const signingInput = `${header}.${payload}`;
170
+ // `slice()` rather than the view: `base64ToBytes` returns a `subarray`, whose backing buffer
171
+ // WebCrypto's `BufferSource` overloads will not accept without a copy of its own bytes.
172
+ const key = await crypto.subtle.importKey('raw', keyBytes.slice().buffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
173
+ const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(signingInput));
174
+ return `${signingInput}.${bytesToBase64Url(new Uint8Array(signature))}`;
175
+ }
176
+ /** The form body an OAuth2 client-credentials exchange posts. */
177
+ export function clientCredentialsBody(auth) {
178
+ const form = new URLSearchParams({
179
+ grant_type: 'client_credentials',
180
+ client_id: auth.clientId,
181
+ client_secret: auth.clientSecret,
182
+ });
183
+ if (auth.scope)
184
+ form.set('scope', auth.scope);
185
+ if (auth.audience)
186
+ form.set('audience', auth.audience);
187
+ return form.toString();
188
+ }
189
+ /**
190
+ * The access token an OAuth2 token response carries, or null when the body is not one.
191
+ *
192
+ * Null rather than a throw so the caller can attach WHICH connection failed and what the provider
193
+ * answered; a bare parse error here would name neither.
194
+ */
195
+ export function readAccessToken(body) {
196
+ if (!body || typeof body !== 'object')
197
+ return null;
198
+ const token = body.access_token;
199
+ return typeof token === 'string' && token.trim() ? token : null;
200
+ }
201
+ //# sourceMappingURL=serviceCatalogAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceCatalogAuth.js","sourceRoot":"","sources":["../../../src/modules/serviceCatalog/serviceCatalogAuth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** Base64 of `bytes`, padded. */
2
+ export declare function bytesToBase64(bytes: Uint8Array): string;
3
+ /** Base64URL of `bytes`, unpadded, as every JWT segment is encoded. */
4
+ export declare function bytesToBase64Url(bytes: Uint8Array): string;
5
+ /** Base64 of `input`'s UTF-8 bytes. */
6
+ export declare function toBase64(input: string): string;
7
+ /** Base64URL of `input`'s UTF-8 bytes. */
8
+ export declare function toBase64Url(input: string): string;
9
+ /**
10
+ * The bytes `value` encodes, or null when it is not base64 at all.
11
+ *
12
+ * Accepts base64URL (`-`/`_`) and a missing tail of `=` padding, because both forms turn up in
13
+ * secrets an operator pasted from somewhere else. Returns NULL rather than a best-effort decode
14
+ * of the prefix: the one caller is deriving an HMAC key, and a key silently decoded from the
15
+ * leading valid characters of a malformed secret would sign every request with the wrong key and
16
+ * fail as an authorization error that names the credential rather than its encoding.
17
+ */
18
+ export declare function base64ToBytes(value: string): Uint8Array | null;
19
+ //# sourceMappingURL=base64.d.ts.map