@cat-factory/integrations 0.166.21 → 0.167.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/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/providers/OpenRouterCatalogService.d.ts.map +1 -1
- package/dist/modules/providers/OpenRouterCatalogService.js +2 -30
- package/dist/modules/providers/OpenRouterCatalogService.js.map +1 -1
- package/dist/modules/providers/openRouterModels.d.ts +12 -0
- package/dist/modules/providers/openRouterModels.d.ts.map +1 -0
- package/dist/modules/providers/openRouterModels.js +107 -0
- package/dist/modules/providers/openRouterModels.js.map +1 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts +75 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.js +410 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.js.map +1 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts +85 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js +177 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js.map +1 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts +173 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.js +362 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.js.map +1 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts +42 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.js +201 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.js.map +1 -0
- package/dist/modules/shared/base64.d.ts +19 -0
- package/dist/modules/shared/base64.d.ts.map +1 -0
- package/dist/modules/shared/base64.js +75 -0
- package/dist/modules/shared/base64.js.map +1 -0
- package/dist/modules/tracker/TicketTrackerService.js +1 -1
- package/dist/modules/tracker/TicketTrackerService.js.map +1 -1
- package/package.json +5 -5
- package/dist/modules/tracker/base64.d.ts +0 -2
- package/dist/modules/tracker/base64.d.ts.map +0 -1
- package/dist/modules/tracker/base64.js +0 -18
- 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":"AACA,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAA;AAG7F,8EAA8E;AAC9E,+FAA+F;AAC/F,qFAAqF;AACrF,EAAE;AACF,+FAA+F;AAC/F,0EAA0E;AAC1E,iGAAiG;AACjG,iGAAiG;AACjG,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAE9E,iEAAiE;AACjE,MAAM,iBAAiB,GAAG,SAAS,CAAA;AAEnC,sFAAsF;AACtF,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAA;AAE3C,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAE7B,+DAA+D;AAC/D,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAiC7C;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,WAAmB;IAC7D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IAC1B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IACzB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,SAAS,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACzE,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IACxD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAA;IAC7C,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC/B,SAAS,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,iBAAiB,CAAC,CAAC,WAAW,EAAE;QAChE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;KAClB,CAAA;AACH,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,CAAA;AACnD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,OAAO,eAAe,CAAC;QACrB,IAAI,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,WAAW,EAAE;QAChD,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,iBAAiB;QAChF,IAAI;KACL,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,cAAc,CAAC,MAAM,CAAC,CAAA;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,MAAuB;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAClE,MAAM,MAAM,GAAG,SAAS,IAAI,SAAS,KAAK,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IAC3F,OAAO,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC,IAAI,IAAI,CAAA;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SACZ,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAwB;IAC/D,QAAQ,IAAI,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACnC,KAAK,SAAS;YACZ,OAAO,SAAS,CAAA;QAClB,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAA;QAClB,KAAK,MAAM;YACT,OAAO,MAAM,CAAA;QACf;YACE,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,MAAM,mBAAmB,GAAG,2BAA2B,CAAA;AAEvD,8EAA8E;AAC9E,MAAM,0BAA0B,GAAG,8BAA8B,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAuB,EACvB,IAAyB;IAEzB,MAAM,EAAE,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACrC,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAA;IACtC,OAAO;QACL,EAAE;QACF,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QAC3E,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC;QAC9B,WAAW,EAAE,kBAAkB,CAAC,MAAM,CAAC;QACvC,YAAY,EAAE,kBAAkB,CAAC,MAAM,CAAC;QACxC,GAAG;QACH,IAAI;KACL,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAuB;IACnD,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,CAAC,CAAA;IAC/D,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAA;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,WAAW,EAAE,CAAA;IACpF,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,yBAAyB,CAAA;IACtF,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAA;IACtC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAA;IAC5E,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACpE,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACpE,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC7E,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,0BAA0B,CAAC,EAAE,IAAI,EAAE,CAAA;IACzE,IAAI,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,CAAA;IAChE,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;IACrC,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;SACjC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;SACjC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IAClD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9D,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACjD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7E,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAA;AACjD,CAAC;AAED,SAAS,YAAY,CAAC,IAAsC;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAA;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAA;IAChC,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;AAC1C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC1C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,eAAe,CAAC,GAAG,CAAC,GAAG,CAAA;AAChD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA;IACvE,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,CAAC,GAAG;YAAE,SAAQ;QAClB,IAAI,wBAAwB,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC,GAAG,CAAC;YAAE,SAAQ;QACvF,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC/B,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACb,IAAI,GAAG,CAAC,MAAM,IAAI,gBAAgB;YAAE,MAAK;IAC3C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACtC,IAAI,CAAC,GAAG;YAAE,SAAQ;QAClB,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;QACtC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAuB;IACzD,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,UAAU,CAAA;IAC1C,IAAI,OAAO,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC/C,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAA;IACjC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,oBAAoB;QAAE,OAAO,IAAI,CAAA;IAClE,MAAM,MAAM,GAAG,wBAAwB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC1D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,OAAO;QACL,EAAE;QACF,KAAK,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QAC5F,MAAM;QACN,UAAU;QACV,GAAG;QACH,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI;KAChD,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,MAAM;IACN,eAAe;IACf,oBAAoB;IACpB,gBAAgB;IAChB,sBAAsB;IACtB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,aAAa;IACb,aAAa;IACb,mBAAmB;CACpB,CAAA;AAED,kGAAkG;AAClG,MAAM,CAAC,MAAM,gBAAgB,GAAsB,CAAC,GAAG,mBAAmB,EAAE,iBAAiB,CAAC,CAAA"}
|
|
@@ -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
|