@kb-labs/marketplace-registry-api 2.96.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/README.md +5 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +321 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
import { ILogger } from '@kb-labs/core-platform';
|
|
3
|
+
import { JwtConfig } from '@kb-labs/gateway-auth';
|
|
4
|
+
import { RegistryService } from '@kb-labs/marketplace-registry-core';
|
|
5
|
+
|
|
6
|
+
interface RegistryServerOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
logger: ILogger;
|
|
10
|
+
registry: RegistryService;
|
|
11
|
+
jwtConfig?: JwtConfig;
|
|
12
|
+
}
|
|
13
|
+
declare function createRegistryServer(opts: RegistryServerOptions): Promise<FastifyInstance>;
|
|
14
|
+
|
|
15
|
+
export { type RegistryServerOptions, createRegistryServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import multipart from '@fastify/multipart';
|
|
2
|
+
import { HttpObservabilityCollector, createDaemonServer, createServiceReadyResponse } from '@kb-labs/shared-http';
|
|
3
|
+
import { verifyAccessToken } from '@kb-labs/gateway-auth';
|
|
4
|
+
|
|
5
|
+
// src/server.ts
|
|
6
|
+
|
|
7
|
+
// src/multipart.ts
|
|
8
|
+
async function parseMultipart(req) {
|
|
9
|
+
const parts = req.parts?.();
|
|
10
|
+
if (!parts) {
|
|
11
|
+
return { tarball: null, fields: {} };
|
|
12
|
+
}
|
|
13
|
+
let tarball = null;
|
|
14
|
+
const fields = {};
|
|
15
|
+
for await (const part of parts) {
|
|
16
|
+
if (part.file) {
|
|
17
|
+
if (part.fieldname === "tarball") {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for await (const chunk of part.file) {
|
|
20
|
+
chunks.push(chunk);
|
|
21
|
+
}
|
|
22
|
+
tarball = Buffer.concat(chunks);
|
|
23
|
+
} else {
|
|
24
|
+
for await (const _ of part.file) {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
fields[part.fieldname] = part.value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { tarball, fields };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/routes/packages.ts
|
|
35
|
+
function registerPackageRoutes(app, registry) {
|
|
36
|
+
app.get("/api/v1/packages", async (req, reply) => {
|
|
37
|
+
const q = req.query?.q;
|
|
38
|
+
const packages = q?.trim() ? await registry.searchPackages(q) : await registry.listPublicPackages();
|
|
39
|
+
return reply.send(packages);
|
|
40
|
+
});
|
|
41
|
+
app.get(
|
|
42
|
+
"/api/v1/packages/:handle/:name",
|
|
43
|
+
async (req, reply) => {
|
|
44
|
+
const { handle, name } = req.params;
|
|
45
|
+
const entry = await registry.getEntry(handle, name);
|
|
46
|
+
if (!entry) {
|
|
47
|
+
return reply.code(404).send({ error: "Package not found" });
|
|
48
|
+
}
|
|
49
|
+
const token = extractShareToken(req);
|
|
50
|
+
const resolvedToken = token ? await registry.resolveShareToken(token) : null;
|
|
51
|
+
const callerNs = req.authContext?.namespaceId ?? null;
|
|
52
|
+
if (!registry.canAccess(entry, callerNs, resolvedToken)) {
|
|
53
|
+
return reply.code(403).send({ error: "Access denied" });
|
|
54
|
+
}
|
|
55
|
+
return reply.send(entry);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
app.get(
|
|
59
|
+
"/api/v1/packages/:handle/:name/stats",
|
|
60
|
+
async (req, reply) => {
|
|
61
|
+
const { handle, name } = req.params;
|
|
62
|
+
return reply.send(await registry.getStats(handle, name));
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
app.get("/api/v1/my/packages", { preHandler: requireAuth }, async (req, reply) => {
|
|
66
|
+
const { namespaceId } = req.authContext;
|
|
67
|
+
const packages = await registry.listByAuthor(namespaceId);
|
|
68
|
+
return reply.send(packages);
|
|
69
|
+
});
|
|
70
|
+
app.post("/api/v1/packages/publish", { preHandler: requireAuth }, async (req, reply) => {
|
|
71
|
+
const { namespaceId } = req.authContext;
|
|
72
|
+
const handle = req.headers["x-author-handle"];
|
|
73
|
+
if (!handle) {
|
|
74
|
+
return reply.code(400).send({ error: "Missing X-Author-Handle header" });
|
|
75
|
+
}
|
|
76
|
+
const { tarball, fields } = await parseMultipart(req);
|
|
77
|
+
if (!tarball) {
|
|
78
|
+
return reply.code(400).send({ error: "Missing tarball file" });
|
|
79
|
+
}
|
|
80
|
+
if (!fields.meta) {
|
|
81
|
+
return reply.code(400).send({ error: "Missing meta field" });
|
|
82
|
+
}
|
|
83
|
+
let publishReq;
|
|
84
|
+
try {
|
|
85
|
+
publishReq = JSON.parse(fields.meta);
|
|
86
|
+
} catch {
|
|
87
|
+
return reply.code(400).send({ error: "Invalid meta JSON" });
|
|
88
|
+
}
|
|
89
|
+
const result = await registry.publish(tarball, publishReq, handle, namespaceId);
|
|
90
|
+
return reply.code(201).send(result);
|
|
91
|
+
});
|
|
92
|
+
app.patch(
|
|
93
|
+
"/api/v1/packages/:handle/:name/meta",
|
|
94
|
+
{ preHandler: requireAuth },
|
|
95
|
+
async (req, reply) => {
|
|
96
|
+
const { handle, name } = req.params;
|
|
97
|
+
await assertOwner(req, registry, handle, name);
|
|
98
|
+
await registry.updateMeta(handle, name, req.body ?? {});
|
|
99
|
+
return reply.code(204).send();
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
app.post(
|
|
103
|
+
"/api/v1/packages/:handle/:name/:version/yank",
|
|
104
|
+
{ preHandler: requireAuth },
|
|
105
|
+
async (req, reply) => {
|
|
106
|
+
const { handle, name, version } = req.params;
|
|
107
|
+
await assertOwner(req, registry, handle, name);
|
|
108
|
+
const { reason } = req.body ?? {};
|
|
109
|
+
await registry.yank(handle, name, version, reason);
|
|
110
|
+
return reply.code(204).send();
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
app.post(
|
|
114
|
+
"/api/v1/packages/:handle/:name/deprecate",
|
|
115
|
+
{ preHandler: requireAuth },
|
|
116
|
+
async (req, reply) => {
|
|
117
|
+
const { handle, name } = req.params;
|
|
118
|
+
await assertOwner(req, registry, handle, name);
|
|
119
|
+
const { message } = req.body ?? {};
|
|
120
|
+
await registry.deprecate(handle, name, message);
|
|
121
|
+
return reply.code(204).send();
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
app.post(
|
|
125
|
+
"/api/v1/packages/:handle/:name/share/allowlist",
|
|
126
|
+
{ preHandler: requireAuth },
|
|
127
|
+
async (req, reply) => {
|
|
128
|
+
const { handle, name } = req.params;
|
|
129
|
+
await assertOwner(req, registry, handle, name);
|
|
130
|
+
const { namespaceId } = req.body ?? {};
|
|
131
|
+
if (!namespaceId) {
|
|
132
|
+
return reply.code(400).send({ error: "Missing namespaceId" });
|
|
133
|
+
}
|
|
134
|
+
await registry.addToAllowlist(handle, name, namespaceId);
|
|
135
|
+
return reply.code(204).send();
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
app.post(
|
|
139
|
+
"/api/v1/packages/:handle/:name/share/token",
|
|
140
|
+
{ preHandler: requireAuth },
|
|
141
|
+
async (req, reply) => {
|
|
142
|
+
const { handle, name } = req.params;
|
|
143
|
+
await assertOwner(req, registry, handle, name);
|
|
144
|
+
const { ttlDays } = req.body ?? {};
|
|
145
|
+
const ttlMs = ttlDays ? ttlDays * 24 * 60 * 60 * 1e3 : void 0;
|
|
146
|
+
const shareToken = await registry.createShareToken(handle, name, ttlMs);
|
|
147
|
+
return reply.code(201).send(shareToken);
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
app.get(
|
|
151
|
+
"/api/v1/packages/:handle/:name/:version/tarball",
|
|
152
|
+
async (req, reply) => {
|
|
153
|
+
const { handle, name, version } = req.params;
|
|
154
|
+
const entry = await registry.getEntry(handle, name);
|
|
155
|
+
if (!entry) {
|
|
156
|
+
return reply.code(404).send({ error: "Package not found" });
|
|
157
|
+
}
|
|
158
|
+
const token = extractShareToken(req);
|
|
159
|
+
const resolvedToken = token ? await registry.resolveShareToken(token) : null;
|
|
160
|
+
const callerNs = req.authContext?.namespaceId ?? null;
|
|
161
|
+
if (!registry.canAccess(entry, callerNs, resolvedToken)) {
|
|
162
|
+
return reply.code(403).send({ error: "Access denied" });
|
|
163
|
+
}
|
|
164
|
+
const { tarball, signature } = await registry.getTarball(handle, name, version);
|
|
165
|
+
await registry.incrementInstalls(handle, name);
|
|
166
|
+
return reply.header("Content-Type", "application/octet-stream").header("Content-Disposition", `attachment; filename="${name}-${version}.tgz"`).header("X-Registry-Signature", signature ? JSON.stringify(signature) : "").send(tarball);
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
app.post(
|
|
170
|
+
"/api/v1/admin/packages/:handle/:name/feature",
|
|
171
|
+
{ preHandler: requireAdmin },
|
|
172
|
+
async (req, reply) => {
|
|
173
|
+
const { handle, name } = req.params;
|
|
174
|
+
const { featured = true } = req.body ?? {};
|
|
175
|
+
await registry.setFeatured(handle, name, featured);
|
|
176
|
+
return reply.code(204).send();
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
function requireAuth(req, reply, done) {
|
|
181
|
+
if (!req.authContext) {
|
|
182
|
+
reply.code(401).send({ error: "Unauthorized" });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
done();
|
|
186
|
+
}
|
|
187
|
+
function requireAdmin(req, reply, done) {
|
|
188
|
+
if (!req.authContext || !req.authContext.permissions?.includes("admin")) {
|
|
189
|
+
reply.code(403).send({ error: "Forbidden" });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
done();
|
|
193
|
+
}
|
|
194
|
+
async function assertOwner(req, registry, handle, name) {
|
|
195
|
+
const entry = await registry.getEntry(handle, name);
|
|
196
|
+
if (!entry) {
|
|
197
|
+
throw Object.assign(new Error("Package not found"), { statusCode: 404 });
|
|
198
|
+
}
|
|
199
|
+
if (entry.authorNamespaceId !== req.authContext.namespaceId) {
|
|
200
|
+
throw Object.assign(new Error("Forbidden"), { statusCode: 403 });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function extractShareToken(req) {
|
|
204
|
+
return req.query?.token ?? req.headers["x-share-token"];
|
|
205
|
+
}
|
|
206
|
+
var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
|
|
207
|
+
"/health",
|
|
208
|
+
"/ready",
|
|
209
|
+
"/metrics",
|
|
210
|
+
"/observability/describe",
|
|
211
|
+
"/observability/health",
|
|
212
|
+
"/docs",
|
|
213
|
+
"/docs/json"
|
|
214
|
+
]);
|
|
215
|
+
function createRegistryAuthMiddleware(jwtConfig) {
|
|
216
|
+
return async function registryAuthMiddleware(request, reply) {
|
|
217
|
+
const pathname = new URL(request.url, "http://localhost").pathname;
|
|
218
|
+
if (PUBLIC_ROUTES.has(pathname) || pathname.startsWith("/docs")) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (request.method === "GET" && (pathname.startsWith("/api/v1/packages") || pathname.startsWith("/api/v1/my"))) ;
|
|
222
|
+
const authHeader = request.headers.authorization;
|
|
223
|
+
const token = authHeader?.match(/^Bearer\s+(.+)$/i)?.[1] ?? null;
|
|
224
|
+
if (!token) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const payload = await verifyAccessToken(token, jwtConfig);
|
|
228
|
+
if (!payload) {
|
|
229
|
+
return reply.code(401).send({ error: "Unauthorized", message: "Invalid token" });
|
|
230
|
+
}
|
|
231
|
+
request.authContext = {
|
|
232
|
+
type: payload.type,
|
|
233
|
+
userId: payload.sub ?? "",
|
|
234
|
+
namespaceId: payload.namespaceId,
|
|
235
|
+
tier: payload.tier,
|
|
236
|
+
permissions: ["host:connect"]
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/server.ts
|
|
242
|
+
function wrapObservability(collector) {
|
|
243
|
+
return {
|
|
244
|
+
register: (server) => collector.register(server),
|
|
245
|
+
buildDescribe: () => collector.buildDescribe(),
|
|
246
|
+
renderPrometheusMetrics: (status, extra) => collector.renderPrometheusMetrics(status, extra),
|
|
247
|
+
buildHealth: (_params) => collector.buildHealth({
|
|
248
|
+
status: "healthy",
|
|
249
|
+
checks: [{ id: "registry-service", status: "ok", message: "Registry service registered" }],
|
|
250
|
+
meta: { serviceHealthEndpoint: "/health" }
|
|
251
|
+
})
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function createRegistryServer(opts) {
|
|
255
|
+
const { logger, registry, jwtConfig } = opts;
|
|
256
|
+
const collector = new HttpObservabilityCollector({
|
|
257
|
+
serviceId: "marketplace-registry",
|
|
258
|
+
serviceType: "http-api",
|
|
259
|
+
version: "0.1.0",
|
|
260
|
+
logsSource: "marketplace-registry"
|
|
261
|
+
});
|
|
262
|
+
const observability = wrapObservability(collector);
|
|
263
|
+
const server = await createDaemonServer({
|
|
264
|
+
serviceId: "marketplace-registry",
|
|
265
|
+
logger,
|
|
266
|
+
observability,
|
|
267
|
+
// Preserve the legacy /health contract { status:'ok', service, ts } — e2e RG-H01.
|
|
268
|
+
healthResponse: () => ({ status: "ok", service: "marketplace-registry", ts: Date.now() }),
|
|
269
|
+
// /ready must return { ready, components } — e2e RG-H02 asserts body.ready === true.
|
|
270
|
+
readyCheck: () => createServiceReadyResponse({
|
|
271
|
+
ready: true,
|
|
272
|
+
components: { registryService: { ready: true } }
|
|
273
|
+
}),
|
|
274
|
+
openapi: {
|
|
275
|
+
title: "KB Labs Marketplace Registry",
|
|
276
|
+
description: "Public registry for KB Labs plugins and adapters"
|
|
277
|
+
},
|
|
278
|
+
setErrorHandler: (app) => {
|
|
279
|
+
app.setErrorHandler((err, request, reply) => {
|
|
280
|
+
const code = err.code ?? "UNKNOWN";
|
|
281
|
+
if (code === "PACKAGE_NOT_FOUND" || code === "VERSION_NOT_FOUND") {
|
|
282
|
+
return reply.code(404).send({ error: "NotFound", code, message: err.message });
|
|
283
|
+
}
|
|
284
|
+
if (code === "VERSION_ALREADY_EXISTS") {
|
|
285
|
+
return reply.code(409).send({ error: "Conflict", code, message: err.message });
|
|
286
|
+
}
|
|
287
|
+
if (code === "TARBALL_TOO_LARGE" || code === "INVALID_MANIFEST") {
|
|
288
|
+
return reply.code(422).send({ error: "UnprocessableEntity", code, message: err.message });
|
|
289
|
+
}
|
|
290
|
+
if (code === "FORBIDDEN" || code === "VERSION_YANKED") {
|
|
291
|
+
return reply.code(403).send({ error: "Forbidden", code, message: err.message });
|
|
292
|
+
}
|
|
293
|
+
if (err.statusCode === 401) {
|
|
294
|
+
return reply.code(401).send({ error: "Unauthorized", code: "UNAUTHORIZED", message: err.message });
|
|
295
|
+
}
|
|
296
|
+
logger.error("Unhandled registry error", err, {
|
|
297
|
+
route: `${request.method} ${request.url}`,
|
|
298
|
+
code
|
|
299
|
+
});
|
|
300
|
+
return reply.code(500).send({ error: "InternalServerError", code, message: err.message });
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
registerRoutes: async (app) => {
|
|
304
|
+
await app.register(multipart, { limits: { fileSize: 55 * 1024 * 1024 } });
|
|
305
|
+
app.decorate("registry", registry);
|
|
306
|
+
app.decorate("observability", collector);
|
|
307
|
+
if (jwtConfig) {
|
|
308
|
+
app.addHook("onRequest", createRegistryAuthMiddleware(jwtConfig));
|
|
309
|
+
}
|
|
310
|
+
await collector.observeOperation(
|
|
311
|
+
"marketplace-registry.bootstrap",
|
|
312
|
+
() => registerPackageRoutes(app, registry)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
return server;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { createRegistryServer };
|
|
320
|
+
//# sourceMappingURL=index.js.map
|
|
321
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/multipart.ts","../src/routes/packages.ts","../src/auth-middleware.ts","../src/server.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { FastifyRequest } from 'fastify';\n\nexport interface MultipartResult {\n tarball: Buffer | null;\n fields: Record<string, string>;\n}\n\n/**\n * Parse a multipart/form-data request.\n * Expects a 'tarball' file field and arbitrary string fields.\n */\nexport async function parseMultipart(req: FastifyRequest): Promise<MultipartResult> {\n const parts = (req as any).parts?.() as AsyncIterable<any> | undefined;\n if (!parts) {\n return { tarball: null, fields: {} };\n }\n\n let tarball: Buffer | null = null;\n const fields: Record<string, string> = {};\n\n for await (const part of parts) {\n if (part.file) {\n if (part.fieldname === 'tarball') {\n const chunks: Buffer[] = [];\n for await (const chunk of part.file) { chunks.push(chunk); }\n tarball = Buffer.concat(chunks);\n } else {\n // Drain unknown file fields\n for await (const _ of part.file) { /* noop */ }\n }\n } else {\n fields[part.fieldname] = part.value as string;\n }\n }\n\n return { tarball, fields };\n}\n","import type { FastifyInstance } from 'fastify';\nimport type { RegistryService } from '@kb-labs/marketplace-registry-core';\nimport type { PublishRequest } from '@kb-labs/marketplace-registry-contracts';\nimport { parseMultipart } from '../multipart.js';\n\nexport function registerPackageRoutes(app: FastifyInstance, registry: RegistryService): void {\n // ── Public: list or search public packages ───────────────────────────────\n app.get<{ Querystring: { q?: string } }>('/api/v1/packages', async (req, reply) => {\n const q = (req.query as any)?.q as string | undefined;\n const packages = q?.trim()\n ? await registry.searchPackages(q)\n : await registry.listPublicPackages();\n return reply.send(packages);\n });\n\n // ── Public: get package metadata ─────────────────────────────────────────\n app.get<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name',\n async (req, reply) => {\n const { handle, name } = req.params;\n const entry = await registry.getEntry(handle, name);\n if (!entry) { return reply.code(404).send({ error: 'Package not found' }); }\n\n const token = extractShareToken(req);\n const resolvedToken = token ? await registry.resolveShareToken(token) : null;\n const callerNs = (req as any).authContext?.namespaceId ?? null;\n\n if (!registry.canAccess(entry, callerNs, resolvedToken)) {\n return reply.code(403).send({ error: 'Access denied' });\n }\n\n return reply.send(entry);\n },\n );\n\n // ── Public: package stats ─────────────────────────────────────────────────\n app.get<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name/stats',\n async (req, reply) => {\n const { handle, name } = req.params;\n return reply.send(await registry.getStats(handle, name));\n },\n );\n\n // ── Auth: list my packages ────────────────────────────────────────────────\n app.get('/api/v1/my/packages', { preHandler: requireAuth }, async (req, reply) => {\n const { namespaceId } = (req as any).authContext;\n const packages = await registry.listByAuthor(namespaceId);\n return reply.send(packages);\n });\n\n // ── Auth: publish (multipart: tarball + meta JSON) ───────────────────────\n app.post('/api/v1/packages/publish', { preHandler: requireAuth }, async (req, reply) => {\n const { namespaceId } = (req as any).authContext;\n const handle = req.headers['x-author-handle'] as string | undefined;\n if (!handle) {\n return reply.code(400).send({ error: 'Missing X-Author-Handle header' });\n }\n\n const { tarball, fields } = await parseMultipart(req);\n if (!tarball) { return reply.code(400).send({ error: 'Missing tarball file' }); }\n if (!fields.meta) { return reply.code(400).send({ error: 'Missing meta field' }); }\n\n let publishReq: PublishRequest;\n try {\n publishReq = JSON.parse(fields.meta) as PublishRequest;\n } catch {\n return reply.code(400).send({ error: 'Invalid meta JSON' });\n }\n\n const result = await registry.publish(tarball, publishReq, handle, namespaceId);\n return reply.code(201).send(result);\n });\n\n // ── Auth: publish metadata only ───────────────────────────────────────────\n app.patch<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name/meta',\n { preHandler: requireAuth },\n async (req, reply) => {\n const { handle, name } = req.params;\n await assertOwner(req, registry, handle, name);\n await registry.updateMeta(handle, name, (req.body as any) ?? {});\n return reply.code(204).send();\n },\n );\n\n // ── Auth: yank version ────────────────────────────────────────────────────\n app.post<{ Params: { handle: string; name: string; version: string } }>(\n '/api/v1/packages/:handle/:name/:version/yank',\n { preHandler: requireAuth },\n async (req, reply) => {\n const { handle, name, version } = req.params;\n await assertOwner(req, registry, handle, name);\n const { reason } = (req.body as any) ?? {};\n await registry.yank(handle, name, version, reason);\n return reply.code(204).send();\n },\n );\n\n // ── Auth: deprecate package ───────────────────────────────────────────────\n app.post<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name/deprecate',\n { preHandler: requireAuth },\n async (req, reply) => {\n const { handle, name } = req.params;\n await assertOwner(req, registry, handle, name);\n const { message } = (req.body as any) ?? {};\n await registry.deprecate(handle, name, message);\n return reply.code(204).send();\n },\n );\n\n // ── Auth: share — add to allowlist ────────────────────────────────────────\n app.post<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name/share/allowlist',\n { preHandler: requireAuth },\n async (req, reply) => {\n const { handle, name } = req.params;\n await assertOwner(req, registry, handle, name);\n const { namespaceId } = (req.body as any) ?? {};\n if (!namespaceId) { return reply.code(400).send({ error: 'Missing namespaceId' }); }\n await registry.addToAllowlist(handle, name, namespaceId);\n return reply.code(204).send();\n },\n );\n\n // ── Auth: share — create link token ──────────────────────────────────────\n app.post<{ Params: { handle: string; name: string } }>(\n '/api/v1/packages/:handle/:name/share/token',\n { preHandler: requireAuth },\n async (req, reply) => {\n const { handle, name } = req.params;\n await assertOwner(req, registry, handle, name);\n const { ttlDays } = (req.body as any) ?? {};\n const ttlMs = ttlDays ? ttlDays * 24 * 60 * 60 * 1000 : undefined;\n const shareToken = await registry.createShareToken(handle, name, ttlMs);\n return reply.code(201).send(shareToken);\n },\n );\n\n // ── Auth: download tarball ────────────────────────────────────────────────\n app.get<{ Params: { handle: string; name: string; version: string } }>(\n '/api/v1/packages/:handle/:name/:version/tarball',\n async (req, reply) => {\n const { handle, name, version } = req.params;\n const entry = await registry.getEntry(handle, name);\n if (!entry) { return reply.code(404).send({ error: 'Package not found' }); }\n\n const token = extractShareToken(req);\n const resolvedToken = token ? await registry.resolveShareToken(token) : null;\n const callerNs = (req as any).authContext?.namespaceId ?? null;\n\n if (!registry.canAccess(entry, callerNs, resolvedToken)) {\n return reply.code(403).send({ error: 'Access denied' });\n }\n\n const { tarball, signature } = await registry.getTarball(handle, name, version);\n await registry.incrementInstalls(handle, name);\n\n return reply\n .header('Content-Type', 'application/octet-stream')\n .header('Content-Disposition', `attachment; filename=\"${name}-${version}.tgz\"`)\n .header('X-Registry-Signature', signature ? JSON.stringify(signature) : '')\n .send(tarball);\n },\n );\n\n // ── Admin: feature/unfeature ──────────────────────────────────────────────\n app.post<{ Params: { handle: string; name: string } }>(\n '/api/v1/admin/packages/:handle/:name/feature',\n { preHandler: requireAdmin },\n async (req, reply) => {\n const { handle, name } = req.params;\n const { featured = true } = (req.body as any) ?? {};\n await registry.setFeatured(handle, name, featured);\n return reply.code(204).send();\n },\n );\n}\n\n// ── Auth helpers ──────────────────────────────────────────────────────────────\n\nfunction requireAuth(req: any, reply: any, done: () => void): void {\n if (!req.authContext) {\n reply.code(401).send({ error: 'Unauthorized' });\n return;\n }\n done();\n}\n\nfunction requireAdmin(req: any, reply: any, done: () => void): void {\n if (!req.authContext || !req.authContext.permissions?.includes('admin')) {\n reply.code(403).send({ error: 'Forbidden' });\n return;\n }\n done();\n}\n\nasync function assertOwner(req: any, registry: RegistryService, handle: string, name: string): Promise<void> {\n const entry = await registry.getEntry(handle, name);\n if (!entry) { throw Object.assign(new Error('Package not found'), { statusCode: 404 }); }\n if (entry.authorNamespaceId !== req.authContext.namespaceId) {\n throw Object.assign(new Error('Forbidden'), { statusCode: 403 });\n }\n}\n\nfunction extractShareToken(req: any): string | undefined {\n return (req.query as any)?.token ?? req.headers['x-share-token'];\n}\n","import type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { AuthContext } from '@kb-labs/gateway-contracts';\nimport { verifyAccessToken, type JwtConfig } from '@kb-labs/gateway-auth';\n\nconst PUBLIC_ROUTES = new Set([\n '/health',\n '/ready',\n '/metrics',\n '/observability/describe',\n '/observability/health',\n '/docs',\n '/docs/json',\n]);\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n authContext?: AuthContext;\n }\n}\n\nexport function createRegistryAuthMiddleware(jwtConfig: JwtConfig) {\n return async function registryAuthMiddleware(\n request: FastifyRequest,\n reply: FastifyReply,\n ): Promise<void> {\n const pathname = new URL(request.url, 'http://localhost').pathname;\n if (PUBLIC_ROUTES.has(pathname) || pathname.startsWith('/docs')) { return; }\n\n // Public read-only routes — no auth needed\n if (request.method === 'GET' && (\n pathname.startsWith('/api/v1/packages') ||\n pathname.startsWith('/api/v1/my') // will be caught by requireAuth in route\n )) {\n // still attempt auth so authContext is set if token is present\n }\n\n const authHeader = request.headers.authorization;\n const token = authHeader?.match(/^Bearer\\s+(.+)$/i)?.[1] ?? null;\n if (!token) { return; } // public routes proceed without authContext; protected routes use requireAuth\n\n const payload = await verifyAccessToken(token, jwtConfig);\n if (!payload) {\n // Token present but invalid — reject immediately\n return reply.code(401).send({ error: 'Unauthorized', message: 'Invalid token' });\n }\n\n request.authContext = {\n type: payload.type,\n userId: payload.sub ?? '',\n namespaceId: payload.namespaceId,\n tier: payload.tier,\n permissions: ['host:connect'],\n };\n };\n}\n","import { type FastifyInstance, type FastifyError } from 'fastify';\nimport multipart from '@fastify/multipart';\nimport {\n HttpObservabilityCollector,\n createDaemonServer,\n createServiceReadyResponse,\n type ObservabilityCollectorLike,\n} from '@kb-labs/shared-http';\nimport type { ILogger } from '@kb-labs/core-platform';\nimport type { ServiceHealthStatus, ObservabilityCheckStatus } from '@kb-labs/core-contracts';\nimport type { JwtConfig } from '@kb-labs/gateway-auth';\nimport type { RegistryService } from '@kb-labs/marketplace-registry-core';\nimport { registerPackageRoutes } from './routes/packages.js';\nimport { createRegistryAuthMiddleware } from './auth-middleware.js';\n\nexport interface RegistryServerOptions {\n port?: number;\n host?: string;\n logger: ILogger;\n registry: RegistryService;\n jwtConfig?: JwtConfig;\n}\n\n/**\n * Wraps HttpObservabilityCollector to satisfy ObservabilityCollectorLike.\n * The buildHealth() no-arg call from createDaemonServer routes uses a fixed\n * \"service registered\" check — the same information the original routes conveyed.\n */\nfunction wrapObservability(collector: HttpObservabilityCollector): ObservabilityCollectorLike {\n return {\n register: (server: FastifyInstance) => collector.register(server),\n buildDescribe: () => collector.buildDescribe(),\n renderPrometheusMetrics: (status: string, extra?: string[]) => collector.renderPrometheusMetrics(status as ServiceHealthStatus, extra),\n buildHealth: (_params?: Record<string, unknown>) =>\n collector.buildHealth({\n status: 'healthy' as ServiceHealthStatus,\n checks: [{ id: 'registry-service', status: 'ok' as ObservabilityCheckStatus, message: 'Registry service registered' }],\n meta: { serviceHealthEndpoint: '/health' },\n }),\n };\n}\n\nexport async function createRegistryServer(opts: RegistryServerOptions): Promise<FastifyInstance> {\n const { logger, registry, jwtConfig } = opts;\n\n const collector = new HttpObservabilityCollector({\n serviceId: 'marketplace-registry',\n serviceType: 'http-api',\n version: '0.1.0',\n logsSource: 'marketplace-registry',\n });\n const observability = wrapObservability(collector);\n\n const server = await createDaemonServer({\n serviceId: 'marketplace-registry',\n logger,\n observability,\n // Preserve the legacy /health contract { status:'ok', service, ts } — e2e RG-H01.\n healthResponse: () => ({ status: 'ok', service: 'marketplace-registry', ts: Date.now() }),\n // /ready must return { ready, components } — e2e RG-H02 asserts body.ready === true.\n readyCheck: () =>\n createServiceReadyResponse({\n ready: true,\n components: { registryService: { ready: true } },\n }),\n openapi: {\n title: 'KB Labs Marketplace Registry',\n description: 'Public registry for KB Labs plugins and adapters',\n },\n setErrorHandler: (app) => {\n app.setErrorHandler((err: FastifyError, request, reply) => {\n const code: string = (err as FastifyError & { code?: string }).code ?? 'UNKNOWN';\n\n if (code === 'PACKAGE_NOT_FOUND' || code === 'VERSION_NOT_FOUND') {\n return reply.code(404).send({ error: 'NotFound', code, message: err.message });\n }\n if (code === 'VERSION_ALREADY_EXISTS') {\n return reply.code(409).send({ error: 'Conflict', code, message: err.message });\n }\n if (code === 'TARBALL_TOO_LARGE' || code === 'INVALID_MANIFEST') {\n return reply.code(422).send({ error: 'UnprocessableEntity', code, message: err.message });\n }\n if (code === 'FORBIDDEN' || code === 'VERSION_YANKED') {\n return reply.code(403).send({ error: 'Forbidden', code, message: err.message });\n }\n if (err.statusCode === 401) {\n return reply.code(401).send({ error: 'Unauthorized', code: 'UNAUTHORIZED', message: err.message });\n }\n // Unhandled error — log full stack so failures surface in service logs.\n logger.error('Unhandled registry error', err, {\n route: `${request.method} ${request.url}`,\n code,\n });\n return reply.code(500).send({ error: 'InternalServerError', code, message: err.message });\n });\n },\n registerRoutes: async (app) => {\n await app.register(multipart, { limits: { fileSize: 55 * 1024 * 1024 } });\n\n app.decorate('registry', registry);\n app.decorate('observability', collector);\n\n if (jwtConfig) {\n app.addHook('onRequest', createRegistryAuthMiddleware(jwtConfig));\n }\n\n await collector.observeOperation('marketplace-registry.bootstrap', () =>\n registerPackageRoutes(app, registry),\n );\n },\n });\n\n return server;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/marketplace-registry-api",
|
|
3
|
+
"description": "HTTP API for KB Labs Marketplace Registry",
|
|
4
|
+
"version": "2.96.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@fastify/multipart": "^9.0.3",
|
|
19
|
+
"fastify": "^5.4.0",
|
|
20
|
+
"@kb-labs/core-contracts": "2.96.0",
|
|
21
|
+
"@kb-labs/core-platform": "2.96.0",
|
|
22
|
+
"@kb-labs/gateway-auth": "2.96.0",
|
|
23
|
+
"@kb-labs/gateway-contracts": "2.96.0",
|
|
24
|
+
"@kb-labs/marketplace-registry-core": "2.96.0",
|
|
25
|
+
"@kb-labs/shared-http": "2.96.0",
|
|
26
|
+
"@kb-labs/marketplace-registry-contracts": "2.96.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.5.0",
|
|
30
|
+
"typescript": "^5",
|
|
31
|
+
"vitest": "^3.2.6",
|
|
32
|
+
"@kb-labs/devkit": "2.96.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.0.0",
|
|
36
|
+
"pnpm": ">=9.0.0"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"clean": "rimraf dist",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"lint": "eslint src --ext .ts",
|
|
44
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
45
|
+
"test": "vitest run --passWithNoTests -c ../../vitest.config.ts",
|
|
46
|
+
"type-check": "tsc --noEmit"
|
|
47
|
+
}
|
|
48
|
+
}
|